Introduction

The object of this project is to perform exploratory data analysis on NHL player statistics, salary, draft year, and demographic information. Later, will predict player salaries from on-ice statistics, and evaluate prediction models for best fit.

library(tidyverse)
library(psych)
library(Hmisc)
library(dplyr)
library(plotly)
library(car)
Loading required package: carData

Attaching package: ‘car’

The following object is masked from ‘package:psych’:

    logit

The following object is masked from ‘package:dplyr’:

    recode

The following object is masked from ‘package:purrr’:

    some

Load the dataset

getwd()
[1] "/Users/sarahmcdonald/Desktop"
#setwd('/Users/sarahmcdonald/Downloads')
nhl<- read.csv('train.csv')
head(nhl,3)

The following is a column legend from https://www.kaggle.com/datasets/camnugent/predict-nhl-player-salaries?resource=download and hockeyabstract.com

colnames(nhl)
  [1] "Salary"       "Born"         "City"         "Pr.St"        "Cntry"        "Nat"          "Ht"           "Wt"          
  [9] "DftYr"        "DftRd"        "Ovrl"         "Hand"         "Last.Name"    "First.Name"   "Position"     "Team"        
 [17] "GP"           "G"            "A"            "A1"           "A2"           "PTS"          "X..."         "E..."        
 [25] "PIM"          "Shifts"       "TOI"          "TOIX"         "TOI.GP"       "TOI.GP.1"     "TOI."         "IPP."        
 [33] "SH."          "SV."          "PDO"          "F.60"         "A.60"         "Pct."         "Diff"         "Diff.60"     
 [41] "iCF"          "iCF.1"        "iFF"          "iSF"          "iSF.1"        "iSF.2"        "ixG"          "iSCF"        
 [49] "iRB"          "iRS"          "iDS"          "sDist"        "sDist.1"      "Pass"         "iHF"          "iHF.1"       
 [57] "iHA"          "iHDf"         "iMiss"        "iGVA"         "iTKA"         "iBLK"         "iGVA.1"       "iTKA.1"      
 [65] "iBLK.1"       "BLK."         "iFOW"         "iFOL"         "iFOW.1"       "iFOL.1"       "FO."          "X.FOT"       
 [73] "dzFOW"        "dzFOL"        "nzFOW"        "nzFOL"        "ozFOW"        "ozFOL"        "FOW.Up"       "FOL.Up"      
 [81] "FOW.Down"     "FOL.Down"     "FOW.Close"    "FOL.Close"    "OTG"          "X1G"          "GWG"          "ENG"         
 [89] "PSG"          "PSA"          "G.Bkhd"       "G.Dflct"      "G.Slap"       "G.Snap"       "G.Tip"        "G.Wrap"      
 [97] "G.Wrst"       "CBar"         "Post"         "Over"         "Wide"         "S.Bkhd"       "S.Dflct"      "S.Slap"      
[105] "S.Snap"       "S.Tip"        "S.Wrap"       "S.Wrst"       "iPenT"        "iPenD"        "iPENT"        "iPEND"       
[113] "iPenDf"       "NPD"          "Min"          "Maj"          "Match"        "Misc"         "Game"         "CF"          
[121] "CA"           "FF"           "FA"           "SF"           "SA"           "xGF"          "xGA"          "SCF"         
[129] "SCA"          "GF"           "GA"           "RBF"          "RBA"          "RSF"          "RSA"          "DSF"         
[137] "DSA"          "FOW"          "FOL"          "HF"           "HA"           "GVA"          "TKA"          "PENT"        
[145] "PEND"         "OPS"          "DPS"          "PS"           "OTOI"         "Grit"         "DAP"          "Pace"        
[153] "GS"           "GS.G"         "Salary_scale"

Column Legend

Acronym - Meaning

%FOT - Percentage of all on-ice faceoffs taken by this player.

+/- - Plus/minus

1G - First goals of a game

A/60 - Events Against per 60 minutes, defaults to Corsi, but can be set to another stat

A1 - First assists, primary assists

A2 - Second assists, secondary assists

BLK% - Percentage of all opposing shot attempts blocked by this player

Born - Birth date

C.Close - A player shot attempt (Corsi) differential when the game was close

C.Down - A player shot attempt (Corsi) differential when the team was trailing

C.Tied - A player shot attempt (Corsi) differential when the team was tied

C.Up - A player shot attempt (Corsi) differential when the team was in the lead

CA - Shot attempts allowed (Corsi, SAT) while this player was on the ice

Cap Hit - The player’s cap hit

CBar - Crossbars hit

CF - The team’s shot attempts (Corsi, SAT) while this player was on the ice

CF.QoC - A weighted average of the Corsi percentage of a player’s opponents

CF.QoT - A weighted average of the Corsi percentage of a player’s linemates

CHIP - Cap Hit of Injured Player is games lost to injury multiplied by cap hit per game

City - City of birth

Cntry - Country of birth

DAP - Disciplined aggression proxy, which is hits and takeaways divided by minor penalties

DFA - Dangerous Fenwick against, which is on-ice unblocked shot attempts weighted by shot quality

DFF - Dangerous Fenwick for, which is on-ice unblocked shot attempts weighted by shot quality

DFF.QoC - Quality of Competition metric based on Dangerous Fenwick, which is unblocked shot attempts weighted for shot quality

DftRd - Round in which the player was drafted

DftYr - Year drafted

Diff - Events for minus event against, defaults to Corsi, but can be set to another stat

Diff/60 - Events for minus event against, per 60 minutes, defaults to Corsi, but can be set to another stat

DPS - Defensive point shares, a catch-all stats that measures a player’s defensive contributions in points in the standings

DSA - Dangerous shots allowed while this player was on the ice, which is rebounds plus rush shots

DSF - The team’s dangerous shots while this player was on the ice, which is rebounds plus rush shots

DZF - Shifts this player has ended with an defensive zone faceoff

dzFOL - Faceoffs lost in the defensive zone

dzFOW - Faceoffs win in the defensive zone

dzGAPF - Team goals allowed after faceoffs taken in the defensive zone

dzGFPF - Team goals scored after faceoffs taken in the defensive zone

DZS - Shifts this player has started with an defensive zone faceoff

dzSAPF - Team shot attempts allowed after faceoffs taken in the defensive zone

dzSFPF - Team shot attempts taken after faceoffs taken in the defensive zone

E+/- - A player’s expected +/-, based on his team and minutes played

ENG - Empty-net goals

Exp dzNGPF - Expected goal differential after faceoffs taken in the defensive zone, based on the number of them

Exp dzNSPF - Expected shot differential after faceoffs taken in the defensive zone, based on the number of them

Exp ozNGPF - Expected goal differential after faceoffs taken in the offensive zone, based on the number of them

Exp ozNSPF - Expected shot differential after faceoffs taken in the offensive zone, based on the number of them

F.Close - A player unblocked shot attempt (Fenwick) differential when the game was close

F.Down - A player unblocked shot attempt (Fenwick) differential when the team was trailing

F.Tied - A player unblocked shot attempt (Fenwick) differential when the team was tied

F.Up - A player unblocked shot attempt (Fenwick) differential when the team was in the lead. Not the best acronym.

F/60 - Events For per 60 minutes, defaults to Corsi, but can be set to another stat

FA - Unblocked shot attempts allowed (Fenwick, USAT) while this player was on the ice

FF - The team’s unblocked shot attempts (Fenwick, USAT) while this player was on the ice

First Name -

FO% - Faceoff winning percentage

FO%vsL - Faceoff winning percentage against lefthanded opponents

FO%vsR - Faceoff winning percentage against righthanded opponents

FOL - The team’s faceoff losses while this player was on the ice

FOL.Close - Faceoffs lost when the score was close

FOL.Down - Faceoffs lost when the team was trailing

FOL.Up - Faceoffs lost when the team was in the lead

FovsL - Faceoffs taken against lefthanded opponents

FovsR - Faceoffs taken against righthanded opponents

FOW - The team’s faceoff wins while this player was on the ice

FOW.Close - Faceoffs won when the score was close

FOW.Down - Faceoffs won when the team was trailing

FOW.Up - Faceoffs won when the team was in the lead

G - Goals

G.Bkhd - Goals scored on the backhand

G.Dflct - Goals scored with deflections

G.Slap - Goals scored with slap shots

G.Snap - Goals scored with snap shots

G.Tip - Goals scored with tip shots

G.Wrap - Goals scored with a wraparound

G.Wrst - Goals scored with a wrist shot

GA - Goals allowed while this player was on the ice

Game - Game Misconduct penalties

GF - The team’s goals while this player was on the ice

GP - Games Played

Grit - Defined as hits, blocked shots, penalty minutes, and majors

GS - The player’s combined game score

GS/G - The player’s average game score

GVA - The team’s giveaways while this player was on the ice

GWG - Game-winning goals

GWG - Game-winning goals

HA - The team’s hits taken while this player was on the ice

Hand - Handedness

HF - The team’s hits thrown while this player was on the ice

HopFO - Opening faceoffs taken at home

HopFOW - Opening faceoffs won at home

Ht - Height

iBLK - Shots blocked by this individual

iCF - Shot attempts (Corsi, SAT) taken by this individual

iDS - Dangerous shots taken by this player, the sum of rebounds and shots off the rush

iFF - Unblocked shot attempts (Fenwick, USAT) taken by this individual

iFOL - Faceoff losses by this individual

iFOW - Faceoff wins by this individual

iGVA - Giveaways by this individual

iHA - Hits taken by this individual

iHDf - The difference in hits thrown by this individual minus those taken

iHF - Hits thrown by this individual

iMiss - Individual shots taken that missed the net.

Injuries - List of types of injuries incurred, if any

iPEND - Penalties drawn by this individual

iPenDf - The difference in penalties drawn minus those taken

iPENT - Penalties taken by this individual

IPP% - Individual points percentage, which is on-ice goals for which this player had the goal or an assist

iRB - Rebound shots taken by this individual

iRS - Shots off the rush taken by this individual

iSCF - All scoring chances taken by this individual

iSF - Shots on goal taken by this individual

iTKA - Takeaways by this individual

ixG - Expected goals (weighted shots) for this individual, which is shot attempts weighted by shot location

Last Name -

Maj - Major penalties taken

Match - Match penalties

MGL - Games lost due to injury

Min - Minor penalties taken

Misc - Misconduct penalties

Nat - Nationality

NGPF - Net Goals Post Faceoff. A differential of all goals within 10 seconds of a faceoff, relative to expectations set by the zone in which they took place

NHLid - NHL player id useful when looking at the raw data in game files

NMC - What kind of no-movement clause this player’s contract has, if any

NPD - Net Penalty Differential is the player’s penalty differential relative to a player of the same position with the same ice time per manpower situation

NSPF - Net Shots Post Faceoff. A differential of all shot attempts within 10 seconds of a faceoff, relative to expectations set by the zone in which they took place

NZF - Shifts this player has ended with a neutral zone faceoff

nzFOL - Faceoffs lost in the neutral zone

nzFOW - Faceoffs won in the neutral zone

nzGAPF - Team goals allowed after faceoffs taken in the neutral zone

nzGFPF - Team goals scored after faceoffs taken in the neutral zone

NZS - Shifts this player has started with a neutral zone faceoff

nzSAPF - Team shot attempts allowed after faceoffs taken in the neutral zone

nzSFPF - Team shot attempts taken after faceoffs taken in the neutral zone

OCA - Shot attempts allowed (Corsi, SAT) while this player was not on the ice

OCF - The team’s shot attempts (Corsi, SAT) while this player was not on the ice

ODZS - Defensive zone faceoffs that occurred without this player on the ice

OFA - Unblocked shot attempts allowed (Fenwick, USAT) while this player was not on the ice

OFF - The team’s unblocked shot attempts (Fenwick, USAT) while this player was not on the ice

OGA - Goals allowed while this player was not on the ice

OGF - The team’s goals while this player was not on the ice

ONZS - Neutral zone faceoffs that occurred without this player on the ice

OOZS - Offensive zone faceoffs that occurred without this player on the ice

OpFO - Opening faceoffs taken

OpFOW - Opening faceoffs won

OppCA60 - A weighted average of the shot attempts (Corsi, SAT) the team allowed per 60 minutes of a player’s opponents

OppCF60 - A weighted average of the shot attempts (Corsi, SAT) the team generated per 60 minutes of a player’s opponents

OppFA60 - A weighted average of the unblocked shot attempts (Fenwick, USAT) the team allowed per 60 minutes of a player’s opponents

OppFF60 - A weighted average of the unblocked shot attempts (Fenwick, USAT) the team generated per 60 minutes of a player’s opponents

OppGA60 - A weighted average of the goals the team allowed per 60 minutes of a player’s opponents

OppGF60 - A weighted average of the goals the team scored per 60 minutes of a player’s opponents

OppSA60 - A weighted average of the shots on goal the team allowed per 60 minutes of a player’s opponents

OppSF60 - A weighted average of the shots on goal the team generated per 60 minutes of a player’s opponents

OPS - Offensive point shares, a catch-all stats that measures a player’s offensive contributions in points in the standings

OSA - Shots on goal allowed while this player was not on the ice

OSCA - Scoring chances allowed while this player was not on the ice

OSCF - The team’s scoring chances while this player was not on the ice

OSF - The team’s shots on goal while this player was not on the ice

OTF - Shifts this player started with an on-the-fly change

OTG - Overtime goals

OTOI - The amount of time this player was not on the ice.

Over - Shots that went over the net

Ovrl - Where the player was drafted overall

OxGA - Expected goals allowed (weighted shots) while this player was not on the ice, which is shot attempts weighted by location

OxGF - The team’s expected goals (weighted shots) while this player was not on the ice, which is shot attempts weighted by location

OZF - Shifts this player has ended with an offensive zone faceoff

ozFO - Faceoffs taken in the offensive zone

ozFOL - Faceoffs lost in the offensive zone

ozFOW - Faceoffs won in the offensive zone

ozGAPF - Team goals allowed after faceoffs taken in the offensive zone

ozGFPF - Team goals scored after faceoffs taken in the offensive zone

OZS - Shifts this player has started with an offensive zone faceoff

ozSAPF - Team shot attempts allowed after faceoffs taken in the offensive zone

ozSFPF - Team shot attempts taken after faceoffs taken in the offensive zone

Pace - The average game pace, as estimated by all shot attempts per 60 minutes

Pass - An estimate of the player’s setup passes (passes that result in a shot attempt)

Pct% - Percentage of all events produced by this team, defaults to Corsi, but can be set to another stat

PDO - The team’s shooting and save percentages added together, times a thousand

PEND - The team’s penalties drawn while this player was on the ice

PENT - The team’s penalties taken while this player was on the ice

PIM - Penalties in minutes

Position - Positions played. NHL source listed first, followed by those listed by any other source.

Post - Times hit the post

Pr/St - Province or state of birth

PS - Point shares, a catch-all stats that measures a player’s contributions in points in the standings

PSA - Penalty shot attempts

PSG - Penalty shot goals

PTS - Points. Goals plus all assists

PTS/60 - Points per 60 minutes

QRelCA60 - Shot attempts allowed per 60 minutes relative to how others did against the same competition

QRelCF60 - Shot attempts per 60 minutes relative to how others did against the same competition

QRelDFA60 - Weighted unblocked shot attempts (Dangeorus Fenwick) allowed per 60 minutes relative to how others did against the same competition

QRelDFF60 - Weighted unblocked shot attempts (Dangeorus Fenwick) per 60 minutes relative to how others did against the same competition

RBA - Rebounds allowed while this player was on the ice. Two very different sources.

RBF - The team’s rebounds while this player was on the ice. Two very different sources.

RelA/60 - The player’s A/60 relative to the team when he’s not on the ice

RelC/60 - Corsi differential per 60 minutes relative to his team

RelC% - Corsi percentage relative to his team

RelDf/60 - The player’s Diff/60 relative to the team when he’s not on the ice

RelF/60 - The player’s F/60 relative to the team when he’s not on the ice

RelF/60 - Fenwick differential per 60 minutes relative to his team

RelF% - Fenwick percentage relative to his team

RelPct% - The players Pct% relative to the team when he’s not on the ice

RelZS% - The player’s zone start percentage when he’s on the ice relative to when he’s not.

RopFO - Opening faceoffs taken at home

RopFOW - Opening faceoffs won at home

RSA - Shots off the rush allowed while this player was on the ice

RSF - The team’s shots off the rush while this player was on the ice

S.Bkhd - Backhand shots

S.Dflct - Deflections

S.Slap - Slap shots

S.Snap - Snap shots

S.Tip - Tipped shots

S.Wrap - Wraparound shots

S.Wrst - Wrist shots

SA - Shots on goal allowed while this player was on the ice

Salary - The player’s salary

SCA - Scoring chances allowed while this player was on the ice

SCF - The team’s scoring chances while this player was on the ice

sDist - The average shot distance of shots taken by this player

SF - The team’s shots on goal while this player was on the ice

SH% - The team’s (not individual’s) shooting percentage when the player was on the ice

SOG - Shootout Goals

SOGDG - Game-deciding shootout goals

SOS - Shootout Shots

Status - This player’s free agency status

SV% - The team’s save percentage when the player was on the ice

Team -

TKA - The team’s takeaways while this player was on the ice

TMCA60 - A weighted average of the shot attempts (Corsi, SAT) the team allowed per 60 minutes of a player’s linemates

TMCF60 - A weighted average of the shot attempts (Corsi, SAT) the team generated per 60 minutes of a player’s linemates

TMFA60 - A weighted average of the unblocked shot attempts (Fenwick, USAT) the team allowed per 60 minutes of a player’s linemates

TMFF60 - A weighted average of the unblocked shot attempts (Fenwick, USAT) the team generated per 60 minutes of a player’s linemates

TMGA60 - A weighted average of the goals the team allowed per 60 minutes of a player’s linemates

TMGF60 - A weighted average of the goals the team scored per 60 minutes of a player’s linemates

TMSA60 - A weighted average of the shots on goal the team allowed per 60 minutes of a player’s linemates

TMSF60 - A weighted average of the shots on goal the team generated per 60 minutes of a player’s linemates

TmxGF - A weighted average of a player’s linemates of the expected goals the team scored

TmxGA - A weighted average of a player’s linemates of the expected goals the team allowed

TMGA - A weighted average of a player’s linemates of the goals the team scored

TMGF - A weighted average of a player’s linemates of the goals the team allowed

TOI - Time on ice, in minutes, or in seconds (NHL)

TOI.QoC - A weighted average of the TOI% of a player’s opponents.

TOI.QoT - A weighted average of the TOI% of a player’s linemates.

TOI/GP - Time on ice divided by games played

TOI% - Percentage of all available ice time assigned to this player.

Wide - Shots that went wide of the net

Wt - Weight

xGA - Expected goals allowed (weighted shots) while this player was on the ice, which is shot attempts weighted by location

xGF - The team’s expected goals (weighted shots) while this player was on the ice, which is shot attempts weighted by location

xGF.QoC - A weighted average of the expected goal percentage of a player’s opponents

xGF.QoT - A weighted average of the expected goal percentage of a player’s linemates

ZS% - Zone start percentage, the percentage of shifts started in the offensive zone, not counting neutral zone or on-the-fly changes

Cleaning

Renaming columns we will be using

colnames(nhl)[colnames(nhl) == "X..."] ="plus_minus"

colnames(nhl)[colnames(nhl) == "E..."] ="E_plus_minus"

colnames(nhl)[colnames(nhl) == "TOI."] ="TOI_pct"

colnames(nhl)[colnames(nhl) == "FO."] ="FO_pct"

We want to predict salary, so let’s adjust the salary scale to be in millions, where $1,000,000 is represented instead as 1 to make visualization easier. Will preserve the original Salary column.

nhl$Salary_scale <- nhl$Salary/1000000

Will view both salary columns side by side to assess if this transformation accomplished what we wanted.

salary_filtered <- nhl %>% 
  select(Salary,Salary_scale)

salary_filtered
NA

Data exploration

Representation of teams

team_count <- dplyr::count(nhl, Team, sort = TRUE)

team_count

We can see the dataset shows traded players using both teams listed together as player team, giving 68 distinct Team representations for the year 30 teams were in the league. Given the salary cap and CBA, a player’s salary won’t change when traded from team to team, so we won’t include this in our model.

Salary visualization

Let’s visualize the range of salaries for the players included in this dataset since that is our target for modeling.

plot1<- ggplot(data = nhl, aes(Salary_scale))+ 
  geom_histogram(fill = 'blue')+ scale_x_continuous(breaks = seq(1, 15, by = 1))

plot1

We can visualize the distribution of salaries vs the mean salary

plot1 + geom_vline(aes(xintercept = mean(Salary_scale)),
                   color = 'red', linetype = 'dashed')

mean_sal<- mean(nhl$Salary_scale)

round(mean_sal,3)
[1] 2.265
describe(nhl$Salary)
nhl$Salary 
       n  missing distinct     Info     Mean      Gmd      .05      .10 
     612        0      138    0.997  2264509  2180437   575000   600000 
     .25      .50      .75      .90      .95 
  742500   925000  3500000  5490000  6862500 

lowest :   575000   590000   595000   600000   615000
highest: 10000000 10900000 11000000 12000000 13800000
sd_sal <- sd(nhl$Salary_scale)
sd_sal
[1] 2.23634

The proportion of salaries below the mean might skew the data when attempting to model it.

Assessing goals vs Salary

ggplot(data = nhl) + 
  geom_point(aes(x = G, y = Salary_scale, color = G))+scale_color_gradient(low = 'purple', high = 'deeppink')

We can see a general positive relationship between goals scored and salary, but there may be more to the scoring prediction than just goals.

cor.test(nhl$G, nhl$Salary_scale)

    Pearson's product-moment correlation

data:  nhl$G and nhl$Salary_scale
t = 15.666, df = 610, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.4766020 0.5898408
sample estimates:
     cor 
0.535625 

Points vs Salary

ggplot (data = nhl) + 
  geom_point(aes(x = PTS, y = Salary_scale, color = PTS))

cor.test(nhl$PTS, nhl$Salary)

    Pearson's product-moment correlation

data:  nhl$PTS and nhl$Salary
t = 19.85, df = 610, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.5757775 0.6723210
sample estimates:
      cor 
0.6264459 

Again there is a high frequency of data points on the low end, so with <25 points and low salary. What could be causing this? The league minimum salary in the season this dataset explores (2016-2017) was $575,000. So if a player played only one game in the NHL that season, their pay rate would be at the league minimum. This explains the high proportion of Salaries below $1 million when we visualized the distribution.

Lets create a new filtered datasets with players making above league minimum and above 1 million and visualize.

nhl_1 <- nhl %>% #players making above league minimum
  filter(Salary_scale >= 0.575)
ggplot(nhl_1, aes(Salary_scale))+
  geom_histogram(fill = 'red') + scale_x_continuous(breaks = seq(1, 15, by = 1))

#plot1<- ggplot(data = nhl, aes(Salary_scale))+ 
 #$ geom_histogram(fill = 'blue')+ scale_x_continuous(breaks = seq(1, 15, by = 1))

There is a minor change, but still not significant.

nhl_2 <- nhl %>% 
  filter(Salary_scale > 1)

ggplot(nhl_2, aes(Salary_scale))+
  geom_histogram(fill = 'green') + scale_x_continuous(breaks = seq(1, 15, by = 1))

This shows a significant change in distribution, a much more normal distribution with positive skew.

cor.test(nhl_2$PTS, nhl_2$Salary)

    Pearson's product-moment correlation

data:  nhl_2$PTS and nhl_2$Salary
t = 11.36, df = 277, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.4779796 0.6389008
sample estimates:
     cor 
0.563767 

We could also filter the dataset for players who played at least half of a season, or 41 games.

nhl_3<- nhl %>% 
  filter(GP >= 41)

ggplot(nhl_3, aes(Salary_scale))+
  geom_histogram(fill = 1:30) + scale_x_continuous(breaks = seq(1, 15, by = 1))

Since we are going to predict salary, it’s best not to filter the data based on salary to avoid too much manipulation on the model. So we will use the 41 games played threshold for our dataset.

ggplot(nhl_3)+ 
  geom_point(aes(x = PTS, y = Salary, color = PTS))

cor.test(nhl_3$PTS, nhl_3$Salary)

    Pearson's product-moment correlation

data:  nhl_3$PTS and nhl_3$Salary
t = 12.036, df = 402, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.4391692 0.5829719
sample estimates:
      cor 
0.5146811 

Finding highest paid players, leading scorers

We can filter this dataset for some of the most commonly measured statistics in hockey: position, team, games played, goals, assists, points, time on ice per game, penalty minutes, expected goals for, and points share.

colnames(nhl_3)
  [1] "Salary"       "Born"         "City"         "Pr.St"        "Cntry"       
  [6] "Nat"          "Ht"           "Wt"           "DftYr"        "DftRd"       
 [11] "Ovrl"         "Hand"         "Last.Name"    "First.Name"   "Position"    
 [16] "Team"         "GP"           "G"            "A"            "A1"          
 [21] "A2"           "PTS"          "X..."         "E..."         "PIM"         
 [26] "Shifts"       "TOI"          "TOIX"         "TOI.GP"       "TOI.GP.1"    
 [31] "TOI."         "IPP."         "SH."          "SV."          "PDO"         
 [36] "F.60"         "A.60"         "Pct."         "Diff"         "Diff.60"     
 [41] "iCF"          "iCF.1"        "iFF"          "iSF"          "iSF.1"       
 [46] "iSF.2"        "ixG"          "iSCF"         "iRB"          "iRS"         
 [51] "iDS"          "sDist"        "sDist.1"      "Pass"         "iHF"         
 [56] "iHF.1"        "iHA"          "iHDf"         "iMiss"        "iGVA"        
 [61] "iTKA"         "iBLK"         "iGVA.1"       "iTKA.1"       "iBLK.1"      
 [66] "BLK."         "iFOW"         "iFOL"         "iFOW.1"       "iFOL.1"      
 [71] "FO."          "X.FOT"        "dzFOW"        "dzFOL"        "nzFOW"       
 [76] "nzFOL"        "ozFOW"        "ozFOL"        "FOW.Up"       "FOL.Up"      
 [81] "FOW.Down"     "FOL.Down"     "FOW.Close"    "FOL.Close"    "OTG"         
 [86] "X1G"          "GWG"          "ENG"          "PSG"          "PSA"         
 [91] "G.Bkhd"       "G.Dflct"      "G.Slap"       "G.Snap"       "G.Tip"       
 [96] "G.Wrap"       "G.Wrst"       "CBar"         "Post"         "Over"        
[101] "Wide"         "S.Bkhd"       "S.Dflct"      "S.Slap"       "S.Snap"      
[106] "S.Tip"        "S.Wrap"       "S.Wrst"       "iPenT"        "iPenD"       
[111] "iPENT"        "iPEND"        "iPenDf"       "NPD"          "Min"         
[116] "Maj"          "Match"        "Misc"         "Game"         "CF"          
[121] "CA"           "FF"           "FA"           "SF"           "SA"          
[126] "xGF"          "xGA"          "SCF"          "SCA"          "GF"          
[131] "GA"           "RBF"          "RBA"          "RSF"          "RSA"         
[136] "DSF"          "DSA"          "FOW"          "FOL"          "HF"          
[141] "HA"           "GVA"          "TKA"          "PENT"         "PEND"        
[146] "OPS"          "DPS"          "PS"           "OTOI"         "Grit"        
[151] "DAP"          "Pace"         "GS"           "GS.G"         "salary_scale"
[156] "Salary_scale"
nhl_4 <- nhl_3 %>% 
  arrange(desc(Salary)) %>% 
  select(Salary, Salary_scale, Last.Name, First.Name, Position,Team, GP, G, A, PTS, plus_minus, E_plus_minus, FO_pct, TOI.GP, TOI_pct, PIM, xGF, GF, xGA, GA, xGA, PS)
nhl_4

We can see that Jonathan Toews and Patrick Kane were the highest paid players that season with salaries of $13.8 million, scoring 58 and 89 points, respectively. Who was the leading points scorer that season?

Leading Scorer

So the highest paid player in Patrick Kane was tied with Sidney Crosby, making $10.9 million for the most points in the league with 89. Let’s see which players scored more points than Jonathan Toews with 58 points.

More Points Than Toews

more_than_toews<- nhl_4 %>% 
  filter(nhl_4$PTS>nhl_4$PTS[2]) %>% 
  arrange(desc(PTS))

more_than_toews

We can see even though Toews was tied for the highest player that year, there were 35 players with more points than Jonathan Toews that season.

Highest Face-off Win Percentage

Toews is known for being a very good face-off player, so lets see how his face off percentage ranked that season.

faceoff<- nhl_4 %>% 
  filter(Position == 'C') %>% 
  arrange(desc(FO_pct)) %>% 
  select(Salary, Last.Name, First.Name, Team, FO_pct)

faceoff
NA

It’s important to note, the statistics selected describe player statistics as goalies are evaluated very differently, so we should filter any goaltenders out of this dataset before we build our model.

nhl_4<- nhl_4 %>% 
  mutate(fwd_or_d = ifelse(Position == 'D', 'D','Forward'))

nhl_4

We can group by position to find some salary information, but should separate by forward vs defense, as many forward positions are listed as “LW/RW” or “C/LW” and we don’t want those treated as separate groups.

nhl_4<- nhl_4 %>% 
  mutate(fwd_or_d = ifelse(Position == 'D', 'D','Forward'))

nhl_4

Descriptive Statistics

We can use this to group by forwards and defense to find mean, max, and minimum salaries amongst the 404 players with 41 or more games played.

max_sal<- nhl_4 %>% 
  group_by(fwd_or_d) %>% 
  summarise(max_salary = max(Salary), mean_salary = mean(Salary), min_salary = min(Salary))

max_sal
NA

It makes sense for the minimum salaries to be equal for both groups given the league minimum salary. Interestingly, forwards have a higher maximum salary but lower mean salary.

We can find the maximum, mean, and minimum salaries by team:

max_sal_team<- nhl_4 %>% 
  group_by(Team) %>% 
  summarise(max_salary = max(Salary), mean_salary = mean(Salary), min_salary = min(Salary))

max_sal_team

Let’s examine the variance and standard deviation in salary within position groups:

var_salary<- nhl_4 %>% 
  group_by(fwd_or_d) %>% 
  summarise(salary_variance = var(Salary), salary_sd = sd(Salary))

var_salary

Before building our model, lets assess values that may be highly correlated with one another, so we can avoid using both factors and artificially increasing our adjusted R - squared by adding another predictor if it is already highly correlated with another.

We know that points includes assists and goals, so those values should be highly correlated.

cor.test(nhl_4$PTS, nhl_4$G)

    Pearson's product-moment correlation

data:  nhl_4$PTS and nhl_4$G
t = 36.094, df = 402, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.8490321 0.8953814
sample estimates:
      cor 
0.8741833 
cor.test(nhl_4$PTS, nhl_4$A)

    Pearson's product-moment correlation

data:  nhl_4$PTS and nhl_4$A
t = 53.001, df = 402, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.9218715 0.9465050
sample estimates:
      cor 
0.9353122 

So using just points in our model should be sufficient.

biserial(nhl_4$PTS, nhl_4$fwd_or_d)

          [,1]
[1,] 0.3367936

Points has a much lower correlation with position, so it may be acceptable to include forward vs defense in our model.

cor.test(nhl_4$PTS, nhl_4$xGF)

    Pearson's product-moment correlation

data:  nhl_4$PTS and nhl_4$xGF
t = 26.539, df = 402, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.7594520 0.8307868
sample estimates:
     cor 
0.797896 

There is also a high correaltion between points and expected goals for, which represents the expected amount of goals scored by a player’s team while that player is on the ice. I expect this will be highly correlated with points share:

cor.test(nhl_4$PS, nhl_4$xGF)

    Pearson's product-moment correlation

data:  nhl_4$PS and nhl_4$xGF
t = 34.599, df = 402, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.8384312 0.8878377
sample estimates:
      cor 
0.8652198 

Building Models

Predicting Salary from points alone:
mod_1<- lm(Salary~ PTS, data = nhl_4)
summary(mod_1)

Call:
lm(formula = Salary ~ PTS, data = nhl_4)

Residuals:
     Min       1Q   Median       3Q      Max 
-5178968 -1303655  -479053  1234023  8962796 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   970242     195590   4.961 1.04e-06 ***
PTS            66672       5539  12.036  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2089000 on 402 degrees of freedom
Multiple R-squared:  0.2649,    Adjusted R-squared:  0.2631 
F-statistic: 144.9 on 1 and 402 DF,  p-value: < 2.2e-16

Here the model has a significant p-alue, adjusted R squared of 0.2631

Predicting Salary from PTS and plus minus
mod_2 <- lm(Salary ~ PTS + plus_minus, data = nhl_4)
summary(mod_2)

Call:
lm(formula = Salary ~ PTS + plus_minus, data = nhl_4)

Residuals:
     Min       1Q   Median       3Q      Max 
-5171634 -1325591  -480902  1247097  8955043 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   993119     201229   4.935 1.18e-06 ***
PTS            65878       5775  11.407  < 2e-16 ***
plus_minus      4418       8987   0.492    0.623    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2091000 on 401 degrees of freedom
Multiple R-squared:  0.2653,    Adjusted R-squared:  0.2617 
F-statistic: 72.42 on 2 and 401 DF,  p-value: < 2.2e-16

The adjusted R-squared is slightly lower, with PTS remaining significant but plus-minus insignificant.

print(AIC(mod_1, k = 1))
[1] 12905.54
print(AIC(mod_2, k=2))
[1] 12910.29

As expected, the lower AIC corresponds to the first model as a better fitting model.

Adding expected goals for and against to the model

Expected GF and expected GA can describe how a player’s team is performing with that player on the ice, even if the player doesn’t directly influence the goals occurring. We can model Salary on xGF and GF, and xGA and GA to see if expected versus actual stats prove to be more significant predictors.

model_GF<- lm(Salary ~xGF+ GF, data = nhl_4)
summary(model_GF)

Call:
lm(formula = Salary ~ xGF + GF, data = nhl_4)

Residuals:
     Min       1Q   Median       3Q      Max 
-5045470 -1158864  -264927  1015947  9486805 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)   
(Intercept)  -207141     262087  -0.790  0.42979   
xGF            39222      13842   2.834  0.00483 **
GF             19826      11963   1.657  0.09825 . 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1986000 on 401 degrees of freedom
Multiple R-squared:  0.337, Adjusted R-squared:  0.3337 
F-statistic: 101.9 on 2 and 401 DF,  p-value: < 2.2e-16

In this case, expected goals for is a more significant predictor than goals for. We can evaluate the same thing with goals against:

model_GA <- lm(Salary ~ xGA + GA, data = nhl_4)
summary(model_GA)

Call:
lm(formula = Salary ~ xGA + GA, data = nhl_4)

Residuals:
     Min       1Q   Median       3Q      Max 
-4410390 -1479158  -485561  1133690  9898661 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)  
(Intercept)   131961     334249   0.395   0.6932  
xGA            23035      14333   1.607   0.1088  
GA             33007      13954   2.365   0.0185 *
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2219000 on 401 degrees of freedom
Multiple R-squared:  0.1724,    Adjusted R-squared:  0.1683 
F-statistic: 41.77 on 2 and 401 DF,  p-value: < 2.2e-16

In this case, expected goals against is not significant, the intercept is not significant, and goals against only meets a 0.05 significance level.

mod_3 <- lm(Salary ~ PTS + xGF, data = nhl_4)
summary(mod_3)

Call:
lm(formula = Salary ~ PTS + xGF, data = nhl_4)

Residuals:
     Min       1Q   Median       3Q      Max 
-5275735 -1191225  -270731  1069698  9327346 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -223570     255579  -0.875    0.382    
PTS            19470       8714   2.234    0.026 *  
xGF            48332       7119   6.789 4.08e-11 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1981000 on 401 degrees of freedom
Multiple R-squared:  0.3407,    Adjusted R-squared:  0.3374 
F-statistic: 103.6 on 2 and 401 DF,  p-value: < 2.2e-16

In these models, xGF, PTS, and GA all show significant affects on the dependent variable, Salary. We can create a model with all three:

mod_4 <- lm(Salary~ PTS + GA + xGF, data = nhl_4)
summary(mod_4)

Call:
lm(formula = Salary ~ PTS + GA + xGF, data = nhl_4)

Residuals:
     Min       1Q   Median       3Q      Max 
-5261703 -1169247  -263778  1043683  9253499 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -321384     288049  -1.116   0.2652    
PTS            21714       9234   2.352   0.0192 *  
GA              5878       7968   0.738   0.4611    
xGF            43479       9697   4.484 9.59e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1982000 on 400 degrees of freedom
Multiple R-squared:  0.3416,    Adjusted R-squared:  0.3366 
F-statistic: 69.17 on 3 and 400 DF,  p-value: < 2.2e-16

When including all three variables, GA loses significance. This could be explained by a relatively strong correlation between xGF and GA, so including both will make one variable drop out of the range of significance.

cor.test(nhl_4$xGF, nhl_4$GA)

    Pearson's product-moment correlation

data:  nhl_4$xGF and nhl_4$GA
t = 20.264, df = 402, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.6589918 0.7559865
sample estimates:
      cor 
0.7108527 

We can confirm that model 3 with fewer predictors is the better model by examining AIC. Notavbly, each of these models have a lower AIC, and therefore better fit, than regressing Salary on points alone.

AIC(mod_4, k = 3)
[1] 12873.03
AIC(mod_3, k=2)
[1] 12866.58

Lastly, we can examine the relationship between Salary and just xGF:

mod_xGF<- lm(Salary~ xGF, data = nhl_4)
summary(mod_xGF)

Call:
lm(formula = Salary ~ xGF, data = nhl_4)

Residuals:
     Min       1Q   Median       3Q      Max 
-4968018 -1205154  -240009  1072044  9621790 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -325448     252724  -1.288    0.199    
xGF            61025       4313  14.150   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1990000 on 402 degrees of freedom
Multiple R-squared:  0.3325,    Adjusted R-squared:  0.3308 
F-statistic: 200.2 on 1 and 402 DF,  p-value: < 2.2e-16
AIC(mod_xGF)
[1] 12869.58

In summary, of the selected predictors and combinations, xGF and PTS are the best predictors of Salary when used as a multivariate regression, rather than either predictor alone.

Visualizing the predictor/response relationship and model

We can view the relationship between PTS, xGF, and Salary(scaled) in a 3D plot:

plot_ly(x = nhl_4$PTS, y = nhl_4$Salary_scale, z = nhl_4$xGF, color = nhl_4$PTS) %>% 
  layout(scene=list(xaxis = list(title = 'Points'), yaxis = list(title='Salary in Millions'),zaxis = list(title = 'Expected Goals For')))
No trace type specified:
  Based on info supplied, a 'scatter3d' trace seems appropriate.
  Read more about this trace type -> https://plotly.com/r/reference/#scatter3d
No scatter3d mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
No trace type specified:
  Based on info supplied, a 'scatter3d' trace seems appropriate.
  Read more about this trace type -> https://plotly.com/r/reference/#scatter3d
No scatter3d mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode

The relationships can also be viewed in two dimensions using the ‘car’ library. In this visualization, the plotted line shows the relationship between the given predictor and the response variable with the other predictor held constant.

avPlots(mod_3)

LS0tCnRpdGxlOiAiTkhMIHN0YXRzIGFuZCBzYWxhcnkgZGF0YSBhbmFseXNpcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBJbnRyb2R1Y3Rpb24KClRoZSBvYmplY3Qgb2YgdGhpcyBwcm9qZWN0IGlzIHRvIHBlcmZvcm0gZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyBvbiBOSEwgcGxheWVyIHN0YXRpc3RpY3MsIHNhbGFyeSwgZHJhZnQgeWVhciwgYW5kIGRlbW9ncmFwaGljIGluZm9ybWF0aW9uLiBMYXRlciwgd2lsbCBwcmVkaWN0IHBsYXllciBzYWxhcmllcyBmcm9tIG9uLWljZSBzdGF0aXN0aWNzLCBhbmQgZXZhbHVhdGUgcHJlZGljdGlvbiBtb2RlbHMgZm9yIGJlc3QgZml0LgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHBzeWNoKQpsaWJyYXJ5KEhtaXNjKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShjYXIpCmBgYAoKIyMjIExvYWQgdGhlIGRhdGFzZXQKCmBgYHtyfQpnZXR3ZCgpCnNldHdkKCcvVXNlcnMvc2FyYWhtY2RvbmFsZC9Eb3dubG9hZHMnKQpgYGAKCmBgYHtyfQpuaGw8LSByZWFkLmNzdigndHJhaW4uY3N2JykKYGBgCgpgYGB7cn0KaGVhZChuaGwsMykKYGBgCgpUaGUgZm9sbG93aW5nIGlzIGEgY29sdW1uIGxlZ2VuZCBmcm9tIDxodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL2NhbW51Z2VudC9wcmVkaWN0LW5obC1wbGF5ZXItc2FsYXJpZXM/cmVzb3VyY2U9ZG93bmxvYWQ+IGFuZCBob2NrZXlhYnN0cmFjdC5jb20KCmBgYHtyfQpjb2xuYW1lcyhuaGwpCmBgYAoKIyMgKipDb2x1bW4gTGVnZW5kKioKCkFjcm9ueW0gLSBNZWFuaW5nCgolRk9UIC0gUGVyY2VudGFnZSBvZiBhbGwgb24taWNlIGZhY2VvZmZzIHRha2VuIGJ5IHRoaXMgcGxheWVyLgoKKy8tIC0gUGx1cy9taW51cwoKMUcgLSBGaXJzdCBnb2FscyBvZiBhIGdhbWUKCkEvNjAgLSBFdmVudHMgQWdhaW5zdCBwZXIgNjAgbWludXRlcywgZGVmYXVsdHMgdG8gQ29yc2ksIGJ1dCBjYW4gYmUgc2V0IHRvIGFub3RoZXIgc3RhdAoKQTEgLSBGaXJzdCBhc3Npc3RzLCBwcmltYXJ5IGFzc2lzdHMKCkEyIC0gU2Vjb25kIGFzc2lzdHMsIHNlY29uZGFyeSBhc3Npc3RzCgpCTEslIC0gUGVyY2VudGFnZSBvZiBhbGwgb3Bwb3Npbmcgc2hvdCBhdHRlbXB0cyBibG9ja2VkIGJ5IHRoaXMgcGxheWVyCgpCb3JuIC0gQmlydGggZGF0ZQoKQy5DbG9zZSAtIEEgcGxheWVyIHNob3QgYXR0ZW1wdCAoQ29yc2kpIGRpZmZlcmVudGlhbCB3aGVuIHRoZSBnYW1lIHdhcyBjbG9zZQoKQy5Eb3duIC0gQSBwbGF5ZXIgc2hvdCBhdHRlbXB0IChDb3JzaSkgZGlmZmVyZW50aWFsIHdoZW4gdGhlIHRlYW0gd2FzIHRyYWlsaW5nCgpDLlRpZWQgLSBBIHBsYXllciBzaG90IGF0dGVtcHQgKENvcnNpKSBkaWZmZXJlbnRpYWwgd2hlbiB0aGUgdGVhbSB3YXMgdGllZAoKQy5VcCAtIEEgcGxheWVyIHNob3QgYXR0ZW1wdCAoQ29yc2kpIGRpZmZlcmVudGlhbCB3aGVuIHRoZSB0ZWFtIHdhcyBpbiB0aGUgbGVhZAoKQ0EgLSBTaG90IGF0dGVtcHRzIGFsbG93ZWQgKENvcnNpLCBTQVQpIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlCgpDYXAgSGl0IC0gVGhlIHBsYXllcidzIGNhcCBoaXQKCkNCYXIgLSBDcm9zc2JhcnMgaGl0CgpDRiAtIFRoZSB0ZWFtJ3Mgc2hvdCBhdHRlbXB0cyAoQ29yc2ksIFNBVCkgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UKCkNGLlFvQyAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgQ29yc2kgcGVyY2VudGFnZSBvZiBhIHBsYXllcidzIG9wcG9uZW50cwoKQ0YuUW9UIC0gQSB3ZWlnaHRlZCBhdmVyYWdlIG9mIHRoZSBDb3JzaSBwZXJjZW50YWdlIG9mIGEgcGxheWVyJ3MgbGluZW1hdGVzCgpDSElQIC0gQ2FwIEhpdCBvZiBJbmp1cmVkIFBsYXllciBpcyBnYW1lcyBsb3N0IHRvIGluanVyeSBtdWx0aXBsaWVkIGJ5IGNhcCBoaXQgcGVyIGdhbWUKCkNpdHkgLSBDaXR5IG9mIGJpcnRoCgpDbnRyeSAtIENvdW50cnkgb2YgYmlydGgKCkRBUCAtIERpc2NpcGxpbmVkIGFnZ3Jlc3Npb24gcHJveHksIHdoaWNoIGlzIGhpdHMgYW5kIHRha2Vhd2F5cyBkaXZpZGVkIGJ5IG1pbm9yIHBlbmFsdGllcwoKREZBIC0gRGFuZ2Vyb3VzIEZlbndpY2sgYWdhaW5zdCwgd2hpY2ggaXMgb24taWNlIHVuYmxvY2tlZCBzaG90IGF0dGVtcHRzIHdlaWdodGVkIGJ5IHNob3QgcXVhbGl0eQoKREZGIC0gRGFuZ2Vyb3VzIEZlbndpY2sgZm9yLCB3aGljaCBpcyBvbi1pY2UgdW5ibG9ja2VkIHNob3QgYXR0ZW1wdHMgd2VpZ2h0ZWQgYnkgc2hvdCBxdWFsaXR5CgpERkYuUW9DIC0gUXVhbGl0eSBvZiBDb21wZXRpdGlvbiBtZXRyaWMgYmFzZWQgb24gRGFuZ2Vyb3VzIEZlbndpY2ssIHdoaWNoIGlzIHVuYmxvY2tlZCBzaG90IGF0dGVtcHRzIHdlaWdodGVkIGZvciBzaG90IHF1YWxpdHkKCkRmdFJkIC0gUm91bmQgaW4gd2hpY2ggdGhlIHBsYXllciB3YXMgZHJhZnRlZAoKRGZ0WXIgLSBZZWFyIGRyYWZ0ZWQKCkRpZmYgLSBFdmVudHMgZm9yIG1pbnVzIGV2ZW50IGFnYWluc3QsIGRlZmF1bHRzIHRvIENvcnNpLCBidXQgY2FuIGJlIHNldCB0byBhbm90aGVyIHN0YXQKCkRpZmYvNjAgLSBFdmVudHMgZm9yIG1pbnVzIGV2ZW50IGFnYWluc3QsIHBlciA2MCBtaW51dGVzLCBkZWZhdWx0cyB0byBDb3JzaSwgYnV0IGNhbiBiZSBzZXQgdG8gYW5vdGhlciBzdGF0CgpEUFMgLSBEZWZlbnNpdmUgcG9pbnQgc2hhcmVzLCBhIGNhdGNoLWFsbCBzdGF0cyB0aGF0IG1lYXN1cmVzIGEgcGxheWVyJ3MgZGVmZW5zaXZlIGNvbnRyaWJ1dGlvbnMgaW4gcG9pbnRzIGluIHRoZSBzdGFuZGluZ3MKCkRTQSAtIERhbmdlcm91cyBzaG90cyBhbGxvd2VkIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlLCB3aGljaCBpcyByZWJvdW5kcyBwbHVzIHJ1c2ggc2hvdHMKCkRTRiAtIFRoZSB0ZWFtJ3MgZGFuZ2Vyb3VzIHNob3RzIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlLCB3aGljaCBpcyByZWJvdW5kcyBwbHVzIHJ1c2ggc2hvdHMKCkRaRiAtIFNoaWZ0cyB0aGlzIHBsYXllciBoYXMgZW5kZWQgd2l0aCBhbiBkZWZlbnNpdmUgem9uZSBmYWNlb2ZmCgpkekZPTCAtIEZhY2VvZmZzIGxvc3QgaW4gdGhlIGRlZmVuc2l2ZSB6b25lCgpkekZPVyAtIEZhY2VvZmZzIHdpbiBpbiB0aGUgZGVmZW5zaXZlIHpvbmUKCmR6R0FQRiAtIFRlYW0gZ29hbHMgYWxsb3dlZCBhZnRlciBmYWNlb2ZmcyB0YWtlbiBpbiB0aGUgZGVmZW5zaXZlIHpvbmUKCmR6R0ZQRiAtIFRlYW0gZ29hbHMgc2NvcmVkIGFmdGVyIGZhY2VvZmZzIHRha2VuIGluIHRoZSBkZWZlbnNpdmUgem9uZQoKRFpTIC0gU2hpZnRzIHRoaXMgcGxheWVyIGhhcyBzdGFydGVkIHdpdGggYW4gZGVmZW5zaXZlIHpvbmUgZmFjZW9mZgoKZHpTQVBGIC0gVGVhbSBzaG90IGF0dGVtcHRzIGFsbG93ZWQgYWZ0ZXIgZmFjZW9mZnMgdGFrZW4gaW4gdGhlIGRlZmVuc2l2ZSB6b25lCgpkelNGUEYgLSBUZWFtIHNob3QgYXR0ZW1wdHMgdGFrZW4gYWZ0ZXIgZmFjZW9mZnMgdGFrZW4gaW4gdGhlIGRlZmVuc2l2ZSB6b25lCgpFKy8tIC0gQSBwbGF5ZXIncyBleHBlY3RlZCArLy0sIGJhc2VkIG9uIGhpcyB0ZWFtIGFuZCBtaW51dGVzIHBsYXllZAoKRU5HIC0gRW1wdHktbmV0IGdvYWxzCgpFeHAgZHpOR1BGIC0gRXhwZWN0ZWQgZ29hbCBkaWZmZXJlbnRpYWwgYWZ0ZXIgZmFjZW9mZnMgdGFrZW4gaW4gdGhlIGRlZmVuc2l2ZSB6b25lLCBiYXNlZCBvbiB0aGUgbnVtYmVyIG9mIHRoZW0KCkV4cCBkek5TUEYgLSBFeHBlY3RlZCBzaG90IGRpZmZlcmVudGlhbCBhZnRlciBmYWNlb2ZmcyB0YWtlbiBpbiB0aGUgZGVmZW5zaXZlIHpvbmUsIGJhc2VkIG9uIHRoZSBudW1iZXIgb2YgdGhlbQoKRXhwIG96TkdQRiAtIEV4cGVjdGVkIGdvYWwgZGlmZmVyZW50aWFsIGFmdGVyIGZhY2VvZmZzIHRha2VuIGluIHRoZSBvZmZlbnNpdmUgem9uZSwgYmFzZWQgb24gdGhlIG51bWJlciBvZiB0aGVtCgpFeHAgb3pOU1BGIC0gRXhwZWN0ZWQgc2hvdCBkaWZmZXJlbnRpYWwgYWZ0ZXIgZmFjZW9mZnMgdGFrZW4gaW4gdGhlIG9mZmVuc2l2ZSB6b25lLCBiYXNlZCBvbiB0aGUgbnVtYmVyIG9mIHRoZW0KCkYuQ2xvc2UgLSBBIHBsYXllciB1bmJsb2NrZWQgc2hvdCBhdHRlbXB0IChGZW53aWNrKSBkaWZmZXJlbnRpYWwgd2hlbiB0aGUgZ2FtZSB3YXMgY2xvc2UKCkYuRG93biAtIEEgcGxheWVyIHVuYmxvY2tlZCBzaG90IGF0dGVtcHQgKEZlbndpY2spIGRpZmZlcmVudGlhbCB3aGVuIHRoZSB0ZWFtIHdhcyB0cmFpbGluZwoKRi5UaWVkIC0gQSBwbGF5ZXIgdW5ibG9ja2VkIHNob3QgYXR0ZW1wdCAoRmVud2ljaykgZGlmZmVyZW50aWFsIHdoZW4gdGhlIHRlYW0gd2FzIHRpZWQKCkYuVXAgLSBBIHBsYXllciB1bmJsb2NrZWQgc2hvdCBhdHRlbXB0IChGZW53aWNrKSBkaWZmZXJlbnRpYWwgd2hlbiB0aGUgdGVhbSB3YXMgaW4gdGhlIGxlYWQuIE5vdCB0aGUgYmVzdCBhY3JvbnltLgoKRi82MCAtIEV2ZW50cyBGb3IgcGVyIDYwIG1pbnV0ZXMsIGRlZmF1bHRzIHRvIENvcnNpLCBidXQgY2FuIGJlIHNldCB0byBhbm90aGVyIHN0YXQKCkZBIC0gVW5ibG9ja2VkIHNob3QgYXR0ZW1wdHMgYWxsb3dlZCAoRmVud2ljaywgVVNBVCkgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UKCkZGIC0gVGhlIHRlYW0ncyB1bmJsb2NrZWQgc2hvdCBhdHRlbXB0cyAoRmVud2ljaywgVVNBVCkgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UKCkZpcnN0IE5hbWUgLQoKRk8lIC0gRmFjZW9mZiB3aW5uaW5nIHBlcmNlbnRhZ2UKCkZPJXZzTCAtIEZhY2VvZmYgd2lubmluZyBwZXJjZW50YWdlIGFnYWluc3QgbGVmdGhhbmRlZCBvcHBvbmVudHMKCkZPJXZzUiAtIEZhY2VvZmYgd2lubmluZyBwZXJjZW50YWdlIGFnYWluc3QgcmlnaHRoYW5kZWQgb3Bwb25lbnRzCgpGT0wgLSBUaGUgdGVhbSdzIGZhY2VvZmYgbG9zc2VzIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlCgpGT0wuQ2xvc2UgLSBGYWNlb2ZmcyBsb3N0IHdoZW4gdGhlIHNjb3JlIHdhcyBjbG9zZQoKRk9MLkRvd24gLSBGYWNlb2ZmcyBsb3N0IHdoZW4gdGhlIHRlYW0gd2FzIHRyYWlsaW5nCgpGT0wuVXAgLSBGYWNlb2ZmcyBsb3N0IHdoZW4gdGhlIHRlYW0gd2FzIGluIHRoZSBsZWFkCgpGb3ZzTCAtIEZhY2VvZmZzIHRha2VuIGFnYWluc3QgbGVmdGhhbmRlZCBvcHBvbmVudHMKCkZvdnNSIC0gRmFjZW9mZnMgdGFrZW4gYWdhaW5zdCByaWdodGhhbmRlZCBvcHBvbmVudHMKCkZPVyAtIFRoZSB0ZWFtJ3MgZmFjZW9mZiB3aW5zIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlCgpGT1cuQ2xvc2UgLSBGYWNlb2ZmcyB3b24gd2hlbiB0aGUgc2NvcmUgd2FzIGNsb3NlCgpGT1cuRG93biAtIEZhY2VvZmZzIHdvbiB3aGVuIHRoZSB0ZWFtIHdhcyB0cmFpbGluZwoKRk9XLlVwIC0gRmFjZW9mZnMgd29uIHdoZW4gdGhlIHRlYW0gd2FzIGluIHRoZSBsZWFkCgpHIC0gR29hbHMKCkcuQmtoZCAtIEdvYWxzIHNjb3JlZCBvbiB0aGUgYmFja2hhbmQKCkcuRGZsY3QgLSBHb2FscyBzY29yZWQgd2l0aCBkZWZsZWN0aW9ucwoKRy5TbGFwIC0gR29hbHMgc2NvcmVkIHdpdGggc2xhcCBzaG90cwoKRy5TbmFwIC0gR29hbHMgc2NvcmVkIHdpdGggc25hcCBzaG90cwoKRy5UaXAgLSBHb2FscyBzY29yZWQgd2l0aCB0aXAgc2hvdHMKCkcuV3JhcCAtIEdvYWxzIHNjb3JlZCB3aXRoIGEgd3JhcGFyb3VuZAoKRy5XcnN0IC0gR29hbHMgc2NvcmVkIHdpdGggYSB3cmlzdCBzaG90CgpHQSAtIEdvYWxzIGFsbG93ZWQgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UKCkdhbWUgLSBHYW1lIE1pc2NvbmR1Y3QgcGVuYWx0aWVzCgpHRiAtIFRoZSB0ZWFtJ3MgZ29hbHMgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UKCkdQIC0gR2FtZXMgUGxheWVkCgpHcml0IC0gRGVmaW5lZCBhcyBoaXRzLCBibG9ja2VkIHNob3RzLCBwZW5hbHR5IG1pbnV0ZXMsIGFuZCBtYWpvcnMKCkdTIC0gVGhlIHBsYXllcidzIGNvbWJpbmVkIGdhbWUgc2NvcmUKCkdTL0cgLSBUaGUgcGxheWVyJ3MgYXZlcmFnZSBnYW1lIHNjb3JlCgpHVkEgLSBUaGUgdGVhbSdzIGdpdmVhd2F5cyB3aGlsZSB0aGlzIHBsYXllciB3YXMgb24gdGhlIGljZQoKR1dHIC0gR2FtZS13aW5uaW5nIGdvYWxzCgpHV0cgLSBHYW1lLXdpbm5pbmcgZ29hbHMKCkhBIC0gVGhlIHRlYW0ncyBoaXRzIHRha2VuIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlCgpIYW5kIC0gSGFuZGVkbmVzcwoKSEYgLSBUaGUgdGVhbSdzIGhpdHMgdGhyb3duIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlCgpIb3BGTyAtIE9wZW5pbmcgZmFjZW9mZnMgdGFrZW4gYXQgaG9tZQoKSG9wRk9XIC0gT3BlbmluZyBmYWNlb2ZmcyB3b24gYXQgaG9tZQoKSHQgLSBIZWlnaHQKCmlCTEsgLSBTaG90cyBibG9ja2VkIGJ5IHRoaXMgaW5kaXZpZHVhbAoKaUNGIC0gU2hvdCBhdHRlbXB0cyAoQ29yc2ksIFNBVCkgdGFrZW4gYnkgdGhpcyBpbmRpdmlkdWFsCgppRFMgLSBEYW5nZXJvdXMgc2hvdHMgdGFrZW4gYnkgdGhpcyBwbGF5ZXIsIHRoZSBzdW0gb2YgcmVib3VuZHMgYW5kIHNob3RzIG9mZiB0aGUgcnVzaAoKaUZGIC0gVW5ibG9ja2VkIHNob3QgYXR0ZW1wdHMgKEZlbndpY2ssIFVTQVQpIHRha2VuIGJ5IHRoaXMgaW5kaXZpZHVhbAoKaUZPTCAtIEZhY2VvZmYgbG9zc2VzIGJ5IHRoaXMgaW5kaXZpZHVhbAoKaUZPVyAtIEZhY2VvZmYgd2lucyBieSB0aGlzIGluZGl2aWR1YWwKCmlHVkEgLSBHaXZlYXdheXMgYnkgdGhpcyBpbmRpdmlkdWFsCgppSEEgLSBIaXRzIHRha2VuIGJ5IHRoaXMgaW5kaXZpZHVhbAoKaUhEZiAtIFRoZSBkaWZmZXJlbmNlIGluIGhpdHMgdGhyb3duIGJ5IHRoaXMgaW5kaXZpZHVhbCBtaW51cyB0aG9zZSB0YWtlbgoKaUhGIC0gSGl0cyB0aHJvd24gYnkgdGhpcyBpbmRpdmlkdWFsCgppTWlzcyAtIEluZGl2aWR1YWwgc2hvdHMgdGFrZW4gdGhhdCBtaXNzZWQgdGhlIG5ldC4KCkluanVyaWVzIC0gTGlzdCBvZiB0eXBlcyBvZiBpbmp1cmllcyBpbmN1cnJlZCwgaWYgYW55CgppUEVORCAtIFBlbmFsdGllcyBkcmF3biBieSB0aGlzIGluZGl2aWR1YWwKCmlQZW5EZiAtIFRoZSBkaWZmZXJlbmNlIGluIHBlbmFsdGllcyBkcmF3biBtaW51cyB0aG9zZSB0YWtlbgoKaVBFTlQgLSBQZW5hbHRpZXMgdGFrZW4gYnkgdGhpcyBpbmRpdmlkdWFsCgpJUFAlIC0gSW5kaXZpZHVhbCBwb2ludHMgcGVyY2VudGFnZSwgd2hpY2ggaXMgb24taWNlIGdvYWxzIGZvciB3aGljaCB0aGlzIHBsYXllciBoYWQgdGhlIGdvYWwgb3IgYW4gYXNzaXN0CgppUkIgLSBSZWJvdW5kIHNob3RzIHRha2VuIGJ5IHRoaXMgaW5kaXZpZHVhbAoKaVJTIC0gU2hvdHMgb2ZmIHRoZSBydXNoIHRha2VuIGJ5IHRoaXMgaW5kaXZpZHVhbAoKaVNDRiAtIEFsbCBzY29yaW5nIGNoYW5jZXMgdGFrZW4gYnkgdGhpcyBpbmRpdmlkdWFsCgppU0YgLSBTaG90cyBvbiBnb2FsIHRha2VuIGJ5IHRoaXMgaW5kaXZpZHVhbAoKaVRLQSAtIFRha2Vhd2F5cyBieSB0aGlzIGluZGl2aWR1YWwKCml4RyAtIEV4cGVjdGVkIGdvYWxzICh3ZWlnaHRlZCBzaG90cykgZm9yIHRoaXMgaW5kaXZpZHVhbCwgd2hpY2ggaXMgc2hvdCBhdHRlbXB0cyB3ZWlnaHRlZCBieSBzaG90IGxvY2F0aW9uCgpMYXN0IE5hbWUgLQoKTWFqIC0gTWFqb3IgcGVuYWx0aWVzIHRha2VuCgpNYXRjaCAtIE1hdGNoIHBlbmFsdGllcwoKTUdMIC0gR2FtZXMgbG9zdCBkdWUgdG8gaW5qdXJ5CgpNaW4gLSBNaW5vciBwZW5hbHRpZXMgdGFrZW4KCk1pc2MgLSBNaXNjb25kdWN0IHBlbmFsdGllcwoKTmF0IC0gTmF0aW9uYWxpdHkKCk5HUEYgLSBOZXQgR29hbHMgUG9zdCBGYWNlb2ZmLiBBIGRpZmZlcmVudGlhbCBvZiBhbGwgZ29hbHMgd2l0aGluIDEwIHNlY29uZHMgb2YgYSBmYWNlb2ZmLCByZWxhdGl2ZSB0byBleHBlY3RhdGlvbnMgc2V0IGJ5IHRoZSB6b25lIGluIHdoaWNoIHRoZXkgdG9vayBwbGFjZQoKTkhMaWQgLSBOSEwgcGxheWVyIGlkIHVzZWZ1bCB3aGVuIGxvb2tpbmcgYXQgdGhlIHJhdyBkYXRhIGluIGdhbWUgZmlsZXMKCk5NQyAtIFdoYXQga2luZCBvZiBuby1tb3ZlbWVudCBjbGF1c2UgdGhpcyBwbGF5ZXIncyBjb250cmFjdCBoYXMsIGlmIGFueQoKTlBEIC0gTmV0IFBlbmFsdHkgRGlmZmVyZW50aWFsIGlzIHRoZSBwbGF5ZXIncyBwZW5hbHR5IGRpZmZlcmVudGlhbCByZWxhdGl2ZSB0byBhIHBsYXllciBvZiB0aGUgc2FtZSBwb3NpdGlvbiB3aXRoIHRoZSBzYW1lIGljZSB0aW1lIHBlciBtYW5wb3dlciBzaXR1YXRpb24KCk5TUEYgLSBOZXQgU2hvdHMgUG9zdCBGYWNlb2ZmLiBBIGRpZmZlcmVudGlhbCBvZiBhbGwgc2hvdCBhdHRlbXB0cyB3aXRoaW4gMTAgc2Vjb25kcyBvZiBhIGZhY2VvZmYsIHJlbGF0aXZlIHRvIGV4cGVjdGF0aW9ucyBzZXQgYnkgdGhlIHpvbmUgaW4gd2hpY2ggdGhleSB0b29rIHBsYWNlCgpOWkYgLSBTaGlmdHMgdGhpcyBwbGF5ZXIgaGFzIGVuZGVkIHdpdGggYSBuZXV0cmFsIHpvbmUgZmFjZW9mZgoKbnpGT0wgLSBGYWNlb2ZmcyBsb3N0IGluIHRoZSBuZXV0cmFsIHpvbmUKCm56Rk9XIC0gRmFjZW9mZnMgd29uIGluIHRoZSBuZXV0cmFsIHpvbmUKCm56R0FQRiAtIFRlYW0gZ29hbHMgYWxsb3dlZCBhZnRlciBmYWNlb2ZmcyB0YWtlbiBpbiB0aGUgbmV1dHJhbCB6b25lCgpuekdGUEYgLSBUZWFtIGdvYWxzIHNjb3JlZCBhZnRlciBmYWNlb2ZmcyB0YWtlbiBpbiB0aGUgbmV1dHJhbCB6b25lCgpOWlMgLSBTaGlmdHMgdGhpcyBwbGF5ZXIgaGFzIHN0YXJ0ZWQgd2l0aCBhIG5ldXRyYWwgem9uZSBmYWNlb2ZmCgpuelNBUEYgLSBUZWFtIHNob3QgYXR0ZW1wdHMgYWxsb3dlZCBhZnRlciBmYWNlb2ZmcyB0YWtlbiBpbiB0aGUgbmV1dHJhbCB6b25lCgpuelNGUEYgLSBUZWFtIHNob3QgYXR0ZW1wdHMgdGFrZW4gYWZ0ZXIgZmFjZW9mZnMgdGFrZW4gaW4gdGhlIG5ldXRyYWwgem9uZQoKT0NBIC0gU2hvdCBhdHRlbXB0cyBhbGxvd2VkIChDb3JzaSwgU0FUKSB3aGlsZSB0aGlzIHBsYXllciB3YXMgbm90IG9uIHRoZSBpY2UKCk9DRiAtIFRoZSB0ZWFtJ3Mgc2hvdCBhdHRlbXB0cyAoQ29yc2ksIFNBVCkgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG5vdCBvbiB0aGUgaWNlCgpPRFpTIC0gRGVmZW5zaXZlIHpvbmUgZmFjZW9mZnMgdGhhdCBvY2N1cnJlZCB3aXRob3V0IHRoaXMgcGxheWVyIG9uIHRoZSBpY2UKCk9GQSAtIFVuYmxvY2tlZCBzaG90IGF0dGVtcHRzIGFsbG93ZWQgKEZlbndpY2ssIFVTQVQpIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBub3Qgb24gdGhlIGljZQoKT0ZGIC0gVGhlIHRlYW0ncyB1bmJsb2NrZWQgc2hvdCBhdHRlbXB0cyAoRmVud2ljaywgVVNBVCkgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG5vdCBvbiB0aGUgaWNlCgpPR0EgLSBHb2FscyBhbGxvd2VkIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBub3Qgb24gdGhlIGljZQoKT0dGIC0gVGhlIHRlYW0ncyBnb2FscyB3aGlsZSB0aGlzIHBsYXllciB3YXMgbm90IG9uIHRoZSBpY2UKCk9OWlMgLSBOZXV0cmFsIHpvbmUgZmFjZW9mZnMgdGhhdCBvY2N1cnJlZCB3aXRob3V0IHRoaXMgcGxheWVyIG9uIHRoZSBpY2UKCk9PWlMgLSBPZmZlbnNpdmUgem9uZSBmYWNlb2ZmcyB0aGF0IG9jY3VycmVkIHdpdGhvdXQgdGhpcyBwbGF5ZXIgb24gdGhlIGljZQoKT3BGTyAtIE9wZW5pbmcgZmFjZW9mZnMgdGFrZW4KCk9wRk9XIC0gT3BlbmluZyBmYWNlb2ZmcyB3b24KCk9wcENBNjAgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIHNob3QgYXR0ZW1wdHMgKENvcnNpLCBTQVQpIHRoZSB0ZWFtIGFsbG93ZWQgcGVyIDYwIG1pbnV0ZXMgb2YgYSBwbGF5ZXIncyBvcHBvbmVudHMKCk9wcENGNjAgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIHNob3QgYXR0ZW1wdHMgKENvcnNpLCBTQVQpIHRoZSB0ZWFtIGdlbmVyYXRlZCBwZXIgNjAgbWludXRlcyBvZiBhIHBsYXllcidzIG9wcG9uZW50cwoKT3BwRkE2MCAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgdW5ibG9ja2VkIHNob3QgYXR0ZW1wdHMgKEZlbndpY2ssIFVTQVQpIHRoZSB0ZWFtIGFsbG93ZWQgcGVyIDYwIG1pbnV0ZXMgb2YgYSBwbGF5ZXIncyBvcHBvbmVudHMKCk9wcEZGNjAgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIHVuYmxvY2tlZCBzaG90IGF0dGVtcHRzIChGZW53aWNrLCBVU0FUKSB0aGUgdGVhbSBnZW5lcmF0ZWQgcGVyIDYwIG1pbnV0ZXMgb2YgYSBwbGF5ZXIncyBvcHBvbmVudHMKCk9wcEdBNjAgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIGdvYWxzIHRoZSB0ZWFtIGFsbG93ZWQgcGVyIDYwIG1pbnV0ZXMgb2YgYSBwbGF5ZXIncyBvcHBvbmVudHMKCk9wcEdGNjAgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIGdvYWxzIHRoZSB0ZWFtIHNjb3JlZCBwZXIgNjAgbWludXRlcyBvZiBhIHBsYXllcidzIG9wcG9uZW50cwoKT3BwU0E2MCAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgc2hvdHMgb24gZ29hbCB0aGUgdGVhbSBhbGxvd2VkIHBlciA2MCBtaW51dGVzIG9mIGEgcGxheWVyJ3Mgb3Bwb25lbnRzCgpPcHBTRjYwIC0gQSB3ZWlnaHRlZCBhdmVyYWdlIG9mIHRoZSBzaG90cyBvbiBnb2FsIHRoZSB0ZWFtIGdlbmVyYXRlZCBwZXIgNjAgbWludXRlcyBvZiBhIHBsYXllcidzIG9wcG9uZW50cwoKT1BTIC0gT2ZmZW5zaXZlIHBvaW50IHNoYXJlcywgYSBjYXRjaC1hbGwgc3RhdHMgdGhhdCBtZWFzdXJlcyBhIHBsYXllcidzIG9mZmVuc2l2ZSBjb250cmlidXRpb25zIGluIHBvaW50cyBpbiB0aGUgc3RhbmRpbmdzCgpPU0EgLSBTaG90cyBvbiBnb2FsIGFsbG93ZWQgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG5vdCBvbiB0aGUgaWNlCgpPU0NBIC0gU2NvcmluZyBjaGFuY2VzIGFsbG93ZWQgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG5vdCBvbiB0aGUgaWNlCgpPU0NGIC0gVGhlIHRlYW0ncyBzY29yaW5nIGNoYW5jZXMgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG5vdCBvbiB0aGUgaWNlCgpPU0YgLSBUaGUgdGVhbSdzIHNob3RzIG9uIGdvYWwgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG5vdCBvbiB0aGUgaWNlCgpPVEYgLSBTaGlmdHMgdGhpcyBwbGF5ZXIgc3RhcnRlZCB3aXRoIGFuIG9uLXRoZS1mbHkgY2hhbmdlCgpPVEcgLSBPdmVydGltZSBnb2FscwoKT1RPSSAtIFRoZSBhbW91bnQgb2YgdGltZSB0aGlzIHBsYXllciB3YXMgbm90IG9uIHRoZSBpY2UuCgpPdmVyIC0gU2hvdHMgdGhhdCB3ZW50IG92ZXIgdGhlIG5ldAoKT3ZybCAtIFdoZXJlIHRoZSBwbGF5ZXIgd2FzIGRyYWZ0ZWQgb3ZlcmFsbAoKT3hHQSAtIEV4cGVjdGVkIGdvYWxzIGFsbG93ZWQgKHdlaWdodGVkIHNob3RzKSB3aGlsZSB0aGlzIHBsYXllciB3YXMgbm90IG9uIHRoZSBpY2UsIHdoaWNoIGlzIHNob3QgYXR0ZW1wdHMgd2VpZ2h0ZWQgYnkgbG9jYXRpb24KCk94R0YgLSBUaGUgdGVhbSdzIGV4cGVjdGVkIGdvYWxzICh3ZWlnaHRlZCBzaG90cykgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG5vdCBvbiB0aGUgaWNlLCB3aGljaCBpcyBzaG90IGF0dGVtcHRzIHdlaWdodGVkIGJ5IGxvY2F0aW9uCgpPWkYgLSBTaGlmdHMgdGhpcyBwbGF5ZXIgaGFzIGVuZGVkIHdpdGggYW4gb2ZmZW5zaXZlIHpvbmUgZmFjZW9mZgoKb3pGTyAtIEZhY2VvZmZzIHRha2VuIGluIHRoZSBvZmZlbnNpdmUgem9uZQoKb3pGT0wgLSBGYWNlb2ZmcyBsb3N0IGluIHRoZSBvZmZlbnNpdmUgem9uZQoKb3pGT1cgLSBGYWNlb2ZmcyB3b24gaW4gdGhlIG9mZmVuc2l2ZSB6b25lCgpvekdBUEYgLSBUZWFtIGdvYWxzIGFsbG93ZWQgYWZ0ZXIgZmFjZW9mZnMgdGFrZW4gaW4gdGhlIG9mZmVuc2l2ZSB6b25lCgpvekdGUEYgLSBUZWFtIGdvYWxzIHNjb3JlZCBhZnRlciBmYWNlb2ZmcyB0YWtlbiBpbiB0aGUgb2ZmZW5zaXZlIHpvbmUKCk9aUyAtIFNoaWZ0cyB0aGlzIHBsYXllciBoYXMgc3RhcnRlZCB3aXRoIGFuIG9mZmVuc2l2ZSB6b25lIGZhY2VvZmYKCm96U0FQRiAtIFRlYW0gc2hvdCBhdHRlbXB0cyBhbGxvd2VkIGFmdGVyIGZhY2VvZmZzIHRha2VuIGluIHRoZSBvZmZlbnNpdmUgem9uZQoKb3pTRlBGIC0gVGVhbSBzaG90IGF0dGVtcHRzIHRha2VuIGFmdGVyIGZhY2VvZmZzIHRha2VuIGluIHRoZSBvZmZlbnNpdmUgem9uZQoKUGFjZSAtIFRoZSBhdmVyYWdlIGdhbWUgcGFjZSwgYXMgZXN0aW1hdGVkIGJ5IGFsbCBzaG90IGF0dGVtcHRzIHBlciA2MCBtaW51dGVzCgpQYXNzIC0gQW4gZXN0aW1hdGUgb2YgdGhlIHBsYXllcidzIHNldHVwIHBhc3NlcyAocGFzc2VzIHRoYXQgcmVzdWx0IGluIGEgc2hvdCBhdHRlbXB0KQoKUGN0JSAtIFBlcmNlbnRhZ2Ugb2YgYWxsIGV2ZW50cyBwcm9kdWNlZCBieSB0aGlzIHRlYW0sIGRlZmF1bHRzIHRvIENvcnNpLCBidXQgY2FuIGJlIHNldCB0byBhbm90aGVyIHN0YXQKClBETyAtIFRoZSB0ZWFtJ3Mgc2hvb3RpbmcgYW5kIHNhdmUgcGVyY2VudGFnZXMgYWRkZWQgdG9nZXRoZXIsIHRpbWVzIGEgdGhvdXNhbmQKClBFTkQgLSBUaGUgdGVhbSdzIHBlbmFsdGllcyBkcmF3biB3aGlsZSB0aGlzIHBsYXllciB3YXMgb24gdGhlIGljZQoKUEVOVCAtIFRoZSB0ZWFtJ3MgcGVuYWx0aWVzIHRha2VuIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlCgpQSU0gLSBQZW5hbHRpZXMgaW4gbWludXRlcwoKUG9zaXRpb24gLSBQb3NpdGlvbnMgcGxheWVkLiBOSEwgc291cmNlIGxpc3RlZCBmaXJzdCwgZm9sbG93ZWQgYnkgdGhvc2UgbGlzdGVkIGJ5IGFueSBvdGhlciBzb3VyY2UuCgpQb3N0IC0gVGltZXMgaGl0IHRoZSBwb3N0CgpQci9TdCAtIFByb3ZpbmNlIG9yIHN0YXRlIG9mIGJpcnRoCgpQUyAtIFBvaW50IHNoYXJlcywgYSBjYXRjaC1hbGwgc3RhdHMgdGhhdCBtZWFzdXJlcyBhIHBsYXllcidzIGNvbnRyaWJ1dGlvbnMgaW4gcG9pbnRzIGluIHRoZSBzdGFuZGluZ3MKClBTQSAtIFBlbmFsdHkgc2hvdCBhdHRlbXB0cwoKUFNHIC0gUGVuYWx0eSBzaG90IGdvYWxzCgpQVFMgLSBQb2ludHMuIEdvYWxzIHBsdXMgYWxsIGFzc2lzdHMKClBUUy82MCAtIFBvaW50cyBwZXIgNjAgbWludXRlcwoKUVJlbENBNjAgLSBTaG90IGF0dGVtcHRzIGFsbG93ZWQgcGVyIDYwIG1pbnV0ZXMgcmVsYXRpdmUgdG8gaG93IG90aGVycyBkaWQgYWdhaW5zdCB0aGUgc2FtZSBjb21wZXRpdGlvbgoKUVJlbENGNjAgLSBTaG90IGF0dGVtcHRzIHBlciA2MCBtaW51dGVzIHJlbGF0aXZlIHRvIGhvdyBvdGhlcnMgZGlkIGFnYWluc3QgdGhlIHNhbWUgY29tcGV0aXRpb24KClFSZWxERkE2MCAtIFdlaWdodGVkIHVuYmxvY2tlZCBzaG90IGF0dGVtcHRzIChEYW5nZW9ydXMgRmVud2ljaykgYWxsb3dlZCBwZXIgNjAgbWludXRlcyByZWxhdGl2ZSB0byBob3cgb3RoZXJzIGRpZCBhZ2FpbnN0IHRoZSBzYW1lIGNvbXBldGl0aW9uCgpRUmVsREZGNjAgLSBXZWlnaHRlZCB1bmJsb2NrZWQgc2hvdCBhdHRlbXB0cyAoRGFuZ2VvcnVzIEZlbndpY2spIHBlciA2MCBtaW51dGVzIHJlbGF0aXZlIHRvIGhvdyBvdGhlcnMgZGlkIGFnYWluc3QgdGhlIHNhbWUgY29tcGV0aXRpb24KClJCQSAtIFJlYm91bmRzIGFsbG93ZWQgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UuIFR3byB2ZXJ5IGRpZmZlcmVudCBzb3VyY2VzLgoKUkJGIC0gVGhlIHRlYW0ncyByZWJvdW5kcyB3aGlsZSB0aGlzIHBsYXllciB3YXMgb24gdGhlIGljZS4gVHdvIHZlcnkgZGlmZmVyZW50IHNvdXJjZXMuCgpSZWxBLzYwIC0gVGhlIHBsYXllcidzIEEvNjAgcmVsYXRpdmUgdG8gdGhlIHRlYW0gd2hlbiBoZSdzIG5vdCBvbiB0aGUgaWNlCgpSZWxDLzYwIC0gQ29yc2kgZGlmZmVyZW50aWFsIHBlciA2MCBtaW51dGVzIHJlbGF0aXZlIHRvIGhpcyB0ZWFtCgpSZWxDJSAtIENvcnNpIHBlcmNlbnRhZ2UgcmVsYXRpdmUgdG8gaGlzIHRlYW0KClJlbERmLzYwIC0gVGhlIHBsYXllcidzIERpZmYvNjAgcmVsYXRpdmUgdG8gdGhlIHRlYW0gd2hlbiBoZSdzIG5vdCBvbiB0aGUgaWNlCgpSZWxGLzYwIC0gVGhlIHBsYXllcidzIEYvNjAgcmVsYXRpdmUgdG8gdGhlIHRlYW0gd2hlbiBoZSdzIG5vdCBvbiB0aGUgaWNlCgpSZWxGLzYwIC0gRmVud2ljayBkaWZmZXJlbnRpYWwgcGVyIDYwIG1pbnV0ZXMgcmVsYXRpdmUgdG8gaGlzIHRlYW0KClJlbEYlIC0gRmVud2ljayBwZXJjZW50YWdlIHJlbGF0aXZlIHRvIGhpcyB0ZWFtCgpSZWxQY3QlIC0gVGhlIHBsYXllcnMgUGN0JSByZWxhdGl2ZSB0byB0aGUgdGVhbSB3aGVuIGhlJ3Mgbm90IG9uIHRoZSBpY2UKClJlbFpTJSAtIFRoZSBwbGF5ZXIncyB6b25lIHN0YXJ0IHBlcmNlbnRhZ2Ugd2hlbiBoZSdzIG9uIHRoZSBpY2UgcmVsYXRpdmUgdG8gd2hlbiBoZSdzIG5vdC4KClJvcEZPIC0gT3BlbmluZyBmYWNlb2ZmcyB0YWtlbiBhdCBob21lCgpSb3BGT1cgLSBPcGVuaW5nIGZhY2VvZmZzIHdvbiBhdCBob21lCgpSU0EgLSBTaG90cyBvZmYgdGhlIHJ1c2ggYWxsb3dlZCB3aGlsZSB0aGlzIHBsYXllciB3YXMgb24gdGhlIGljZQoKUlNGIC0gVGhlIHRlYW0ncyBzaG90cyBvZmYgdGhlIHJ1c2ggd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UKClMuQmtoZCAtIEJhY2toYW5kIHNob3RzCgpTLkRmbGN0IC0gRGVmbGVjdGlvbnMKClMuU2xhcCAtIFNsYXAgc2hvdHMKClMuU25hcCAtIFNuYXAgc2hvdHMKClMuVGlwIC0gVGlwcGVkIHNob3RzCgpTLldyYXAgLSBXcmFwYXJvdW5kIHNob3RzCgpTLldyc3QgLSBXcmlzdCBzaG90cwoKU0EgLSBTaG90cyBvbiBnb2FsIGFsbG93ZWQgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UKClNhbGFyeSAtIFRoZSBwbGF5ZXIncyBzYWxhcnkKClNDQSAtIFNjb3JpbmcgY2hhbmNlcyBhbGxvd2VkIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlCgpTQ0YgLSBUaGUgdGVhbSdzIHNjb3JpbmcgY2hhbmNlcyB3aGlsZSB0aGlzIHBsYXllciB3YXMgb24gdGhlIGljZQoKc0Rpc3QgLSBUaGUgYXZlcmFnZSBzaG90IGRpc3RhbmNlIG9mIHNob3RzIHRha2VuIGJ5IHRoaXMgcGxheWVyCgpTRiAtIFRoZSB0ZWFtJ3Mgc2hvdHMgb24gZ29hbCB3aGlsZSB0aGlzIHBsYXllciB3YXMgb24gdGhlIGljZQoKU0glIC0gVGhlIHRlYW0ncyAobm90IGluZGl2aWR1YWwncykgc2hvb3RpbmcgcGVyY2VudGFnZSB3aGVuIHRoZSBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UKClNPRyAtIFNob290b3V0IEdvYWxzCgpTT0dERyAtIEdhbWUtZGVjaWRpbmcgc2hvb3RvdXQgZ29hbHMKClNPUyAtIFNob290b3V0IFNob3RzCgpTdGF0dXMgLSBUaGlzIHBsYXllcidzIGZyZWUgYWdlbmN5IHN0YXR1cwoKU1YlIC0gVGhlIHRlYW0ncyBzYXZlIHBlcmNlbnRhZ2Ugd2hlbiB0aGUgcGxheWVyIHdhcyBvbiB0aGUgaWNlCgpUZWFtIC0KClRLQSAtIFRoZSB0ZWFtJ3MgdGFrZWF3YXlzIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlCgpUTUNBNjAgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIHNob3QgYXR0ZW1wdHMgKENvcnNpLCBTQVQpIHRoZSB0ZWFtIGFsbG93ZWQgcGVyIDYwIG1pbnV0ZXMgb2YgYSBwbGF5ZXIncyBsaW5lbWF0ZXMKClRNQ0Y2MCAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgc2hvdCBhdHRlbXB0cyAoQ29yc2ksIFNBVCkgdGhlIHRlYW0gZ2VuZXJhdGVkIHBlciA2MCBtaW51dGVzIG9mIGEgcGxheWVyJ3MgbGluZW1hdGVzCgpUTUZBNjAgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIHVuYmxvY2tlZCBzaG90IGF0dGVtcHRzIChGZW53aWNrLCBVU0FUKSB0aGUgdGVhbSBhbGxvd2VkIHBlciA2MCBtaW51dGVzIG9mIGEgcGxheWVyJ3MgbGluZW1hdGVzCgpUTUZGNjAgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIHVuYmxvY2tlZCBzaG90IGF0dGVtcHRzIChGZW53aWNrLCBVU0FUKSB0aGUgdGVhbSBnZW5lcmF0ZWQgcGVyIDYwIG1pbnV0ZXMgb2YgYSBwbGF5ZXIncyBsaW5lbWF0ZXMKClRNR0E2MCAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgZ29hbHMgdGhlIHRlYW0gYWxsb3dlZCBwZXIgNjAgbWludXRlcyBvZiBhIHBsYXllcidzIGxpbmVtYXRlcwoKVE1HRjYwIC0gQSB3ZWlnaHRlZCBhdmVyYWdlIG9mIHRoZSBnb2FscyB0aGUgdGVhbSBzY29yZWQgcGVyIDYwIG1pbnV0ZXMgb2YgYSBwbGF5ZXIncyBsaW5lbWF0ZXMKClRNU0E2MCAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgc2hvdHMgb24gZ29hbCB0aGUgdGVhbSBhbGxvd2VkIHBlciA2MCBtaW51dGVzIG9mIGEgcGxheWVyJ3MgbGluZW1hdGVzCgpUTVNGNjAgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIHNob3RzIG9uIGdvYWwgdGhlIHRlYW0gZ2VuZXJhdGVkIHBlciA2MCBtaW51dGVzIG9mIGEgcGxheWVyJ3MgbGluZW1hdGVzCgpUbXhHRiAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiBhIHBsYXllcidzIGxpbmVtYXRlcyBvZiB0aGUgZXhwZWN0ZWQgZ29hbHMgdGhlIHRlYW0gc2NvcmVkCgpUbXhHQSAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiBhIHBsYXllcidzIGxpbmVtYXRlcyBvZiB0aGUgZXhwZWN0ZWQgZ29hbHMgdGhlIHRlYW0gYWxsb3dlZAoKVE1HQSAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiBhIHBsYXllcidzIGxpbmVtYXRlcyBvZiB0aGUgZ29hbHMgdGhlIHRlYW0gc2NvcmVkCgpUTUdGIC0gQSB3ZWlnaHRlZCBhdmVyYWdlIG9mIGEgcGxheWVyJ3MgbGluZW1hdGVzIG9mIHRoZSBnb2FscyB0aGUgdGVhbSBhbGxvd2VkCgpUT0kgLSBUaW1lIG9uIGljZSwgaW4gbWludXRlcywgb3IgaW4gc2Vjb25kcyAoTkhMKQoKVE9JLlFvQyAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgVE9JJSBvZiBhIHBsYXllcidzIG9wcG9uZW50cy4KClRPSS5Rb1QgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIFRPSSUgb2YgYSBwbGF5ZXIncyBsaW5lbWF0ZXMuCgpUT0kvR1AgLSBUaW1lIG9uIGljZSBkaXZpZGVkIGJ5IGdhbWVzIHBsYXllZAoKVE9JJSAtIFBlcmNlbnRhZ2Ugb2YgYWxsIGF2YWlsYWJsZSBpY2UgdGltZSBhc3NpZ25lZCB0byB0aGlzIHBsYXllci4KCldpZGUgLSBTaG90cyB0aGF0IHdlbnQgd2lkZSBvZiB0aGUgbmV0CgpXdCAtIFdlaWdodAoKeEdBIC0gRXhwZWN0ZWQgZ29hbHMgYWxsb3dlZCAod2VpZ2h0ZWQgc2hvdHMpIHdoaWxlIHRoaXMgcGxheWVyIHdhcyBvbiB0aGUgaWNlLCB3aGljaCBpcyBzaG90IGF0dGVtcHRzIHdlaWdodGVkIGJ5IGxvY2F0aW9uCgp4R0YgLSBUaGUgdGVhbSdzIGV4cGVjdGVkIGdvYWxzICh3ZWlnaHRlZCBzaG90cykgd2hpbGUgdGhpcyBwbGF5ZXIgd2FzIG9uIHRoZSBpY2UsIHdoaWNoIGlzIHNob3QgYXR0ZW1wdHMgd2VpZ2h0ZWQgYnkgbG9jYXRpb24KCnhHRi5Rb0MgLSBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIGV4cGVjdGVkIGdvYWwgcGVyY2VudGFnZSBvZiBhIHBsYXllcidzIG9wcG9uZW50cwoKeEdGLlFvVCAtIEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgZXhwZWN0ZWQgZ29hbCBwZXJjZW50YWdlIG9mIGEgcGxheWVyJ3MgbGluZW1hdGVzCgpaUyUgLSBab25lIHN0YXJ0IHBlcmNlbnRhZ2UsIHRoZSBwZXJjZW50YWdlIG9mIHNoaWZ0cyBzdGFydGVkIGluIHRoZSBvZmZlbnNpdmUgem9uZSwgbm90IGNvdW50aW5nIG5ldXRyYWwgem9uZSBvciBvbi10aGUtZmx5IGNoYW5nZXMKCiMgQ2xlYW5pbmcKClJlbmFtaW5nIGNvbHVtbnMgd2Ugd2lsbCBiZSB1c2luZwoKYGBge3J9CmNvbG5hbWVzKG5obClbY29sbmFtZXMobmhsKSA9PSAiWC4uLiJdID0icGx1c19taW51cyIKCmNvbG5hbWVzKG5obClbY29sbmFtZXMobmhsKSA9PSAiRS4uLiJdID0iRV9wbHVzX21pbnVzIgoKY29sbmFtZXMobmhsKVtjb2xuYW1lcyhuaGwpID09ICJUT0kuIl0gPSJUT0lfcGN0IgoKY29sbmFtZXMobmhsKVtjb2xuYW1lcyhuaGwpID09ICJGTy4iXSA9IkZPX3BjdCIKYGBgCgpXZSB3YW50IHRvIHByZWRpY3Qgc2FsYXJ5LCBzbyBsZXQncyBhZGp1c3QgdGhlIHNhbGFyeSBzY2FsZSB0byBiZSBpbiBtaWxsaW9ucywgd2hlcmUgXCQxLDAwMCwwMDAgaXMgcmVwcmVzZW50ZWQgaW5zdGVhZCBhcyAxIHRvIG1ha2UgdmlzdWFsaXphdGlvbiBlYXNpZXIuIFdpbGwgcHJlc2VydmUgdGhlIG9yaWdpbmFsIFNhbGFyeSBjb2x1bW4uCgpgYGB7cn0KbmhsJFNhbGFyeV9zY2FsZSA8LSBuaGwkU2FsYXJ5LzEwMDAwMDAKYGBgCgpXaWxsIHZpZXcgYm90aCBzYWxhcnkgY29sdW1ucyBzaWRlIGJ5IHNpZGUgdG8gYXNzZXNzIGlmIHRoaXMgdHJhbnNmb3JtYXRpb24gYWNjb21wbGlzaGVkIHdoYXQgd2Ugd2FudGVkLgoKYGBge3J9CnNhbGFyeV9maWx0ZXJlZCA8LSBuaGwgJT4lIAogIHNlbGVjdChTYWxhcnksU2FsYXJ5X3NjYWxlKQoKc2FsYXJ5X2ZpbHRlcmVkCgpgYGAKCiMgRGF0YSBleHBsb3JhdGlvbgoKIyMjIFJlcHJlc2VudGF0aW9uIG9mIHRlYW1zCgpgYGB7cn0KdGVhbV9jb3VudCA8LSBkcGx5cjo6Y291bnQobmhsLCBUZWFtLCBzb3J0ID0gVFJVRSkKCnRlYW1fY291bnQKYGBgCgpXZSBjYW4gc2VlIHRoZSBkYXRhc2V0IHNob3dzIHRyYWRlZCBwbGF5ZXJzIHVzaW5nIGJvdGggdGVhbXMgbGlzdGVkIHRvZ2V0aGVyIGFzIHBsYXllciB0ZWFtLCBnaXZpbmcgNjggZGlzdGluY3QgVGVhbSByZXByZXNlbnRhdGlvbnMgZm9yIHRoZSB5ZWFyIDMwIHRlYW1zIHdlcmUgaW4gdGhlIGxlYWd1ZS4gR2l2ZW4gdGhlIHNhbGFyeSBjYXAgYW5kIENCQSwgYSBwbGF5ZXIncyBzYWxhcnkgd29uJ3QgY2hhbmdlIHdoZW4gdHJhZGVkIGZyb20gdGVhbSB0byB0ZWFtLCBzbyB3ZSB3b24ndCBpbmNsdWRlIHRoaXMgaW4gb3VyIG1vZGVsLgoKIyMjIFNhbGFyeSB2aXN1YWxpemF0aW9uCgpMZXQncyB2aXN1YWxpemUgdGhlIHJhbmdlIG9mIHNhbGFyaWVzIGZvciB0aGUgcGxheWVycyBpbmNsdWRlZCBpbiB0aGlzIGRhdGFzZXQgc2luY2UgdGhhdCBpcyBvdXIgdGFyZ2V0IGZvciBtb2RlbGluZy4KCmBgYHtyfQpwbG90MTwtIGdncGxvdChkYXRhID0gbmhsLCBhZXMoU2FsYXJ5X3NjYWxlKSkrIAogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAnYmx1ZScpKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDEsIDE1LCBieSA9IDEpKQoKcGxvdDEKCmBgYAoKV2UgY2FuIHZpc3VhbGl6ZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHNhbGFyaWVzIHZzIHRoZSBtZWFuIHNhbGFyeQoKYGBge3J9CnBsb3QxICsgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lYW4oU2FsYXJ5X3NjYWxlKSksCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICdyZWQnLCBsaW5ldHlwZSA9ICdkYXNoZWQnKQpgYGAKCmBgYHtyfQptZWFuX3NhbDwtIG1lYW4obmhsJFNhbGFyeV9zY2FsZSkKCnJvdW5kKG1lYW5fc2FsLDMpCmBgYAoKYGBge3J9CmRlc2NyaWJlKG5obCRTYWxhcnkpCmBgYAoKYGBge3J9CnNkX3NhbCA8LSBzZChuaGwkU2FsYXJ5X3NjYWxlKQpzZF9zYWwKYGBgCgpUaGUgcHJvcG9ydGlvbiBvZiBzYWxhcmllcyBiZWxvdyB0aGUgbWVhbiBtaWdodCBza2V3IHRoZSBkYXRhIHdoZW4gYXR0ZW1wdGluZyB0byBtb2RlbCBpdC4KCiMjIyBBc3Nlc3NpbmcgZ29hbHMgdnMgU2FsYXJ5CgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBuaGwpICsgCiAgZ2VvbV9wb2ludChhZXMoeCA9IEcsIHkgPSBTYWxhcnlfc2NhbGUsIGNvbG9yID0gRykpK3NjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICdwdXJwbGUnLCBoaWdoID0gJ2RlZXBwaW5rJykKYGBgCgpXZSBjYW4gc2VlIGEgZ2VuZXJhbCBwb3NpdGl2ZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBnb2FscyBzY29yZWQgYW5kIHNhbGFyeSwgYnV0IHRoZXJlIG1heSBiZSBtb3JlIHRvIHRoZSBzY29yaW5nIHByZWRpY3Rpb24gdGhhbiBqdXN0IGdvYWxzLgoKYGBge3J9CmNvci50ZXN0KG5obCRHLCBuaGwkU2FsYXJ5X3NjYWxlKQpgYGAKCiMjIyBQb2ludHMgdnMgU2FsYXJ5CgpgYGB7cn0KZ2dwbG90IChkYXRhID0gbmhsKSArIAogIGdlb21fcG9pbnQoYWVzKHggPSBQVFMsIHkgPSBTYWxhcnlfc2NhbGUsIGNvbG9yID0gUFRTKSkKYGBgCgpgYGB7cn0KY29yLnRlc3QobmhsJFBUUywgbmhsJFNhbGFyeSkKYGBgCgpBZ2FpbiB0aGVyZSBpcyBhIGhpZ2ggZnJlcXVlbmN5IG9mIGRhdGEgcG9pbnRzIG9uIHRoZSBsb3cgZW5kLCBzbyB3aXRoIFw8MjUgcG9pbnRzIGFuZCBsb3cgc2FsYXJ5LiBXaGF0IGNvdWxkIGJlIGNhdXNpbmcgdGhpcz8gVGhlIGxlYWd1ZSBtaW5pbXVtIHNhbGFyeSBpbiB0aGUgc2Vhc29uIHRoaXMgZGF0YXNldCBleHBsb3JlcyAoMjAxNi0yMDE3KSB3YXMgXCQ1NzUsMDAwLiBTbyBpZiBhIHBsYXllciBwbGF5ZWQgb25seSBvbmUgZ2FtZSBpbiB0aGUgTkhMIHRoYXQgc2Vhc29uLCB0aGVpciBwYXkgcmF0ZSB3b3VsZCBiZSBhdCB0aGUgbGVhZ3VlIG1pbmltdW0uIFRoaXMgZXhwbGFpbnMgdGhlIGhpZ2ggcHJvcG9ydGlvbiBvZiBTYWxhcmllcyBiZWxvdyBcJDEgbWlsbGlvbiB3aGVuIHdlIHZpc3VhbGl6ZWQgdGhlIGRpc3RyaWJ1dGlvbi4KCkxldHMgY3JlYXRlIGEgbmV3IGZpbHRlcmVkIGRhdGFzZXRzIHdpdGggcGxheWVycyBtYWtpbmcgYWJvdmUgbGVhZ3VlIG1pbmltdW0gYW5kIGFib3ZlIDEgbWlsbGlvbiBhbmQgdmlzdWFsaXplLgoKYGBge3J9Cm5obF8xIDwtIG5obCAlPiUgI3BsYXllcnMgbWFraW5nIGFib3ZlIGxlYWd1ZSBtaW5pbXVtCiAgZmlsdGVyKFNhbGFyeV9zY2FsZSA+PSAwLjU3NSkKYGBgCgpgYGB7cn0KZ2dwbG90KG5obF8xLCBhZXMoU2FsYXJ5X3NjYWxlKSkrCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICdyZWQnKSArIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgMTUsIGJ5ID0gMSkpCiNwbG90MTwtIGdncGxvdChkYXRhID0gbmhsLCBhZXMoU2FsYXJ5X3NjYWxlKSkrIAogIyQgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICdibHVlJykrIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgMTUsIGJ5ID0gMSkpCmBgYAoKVGhlcmUgaXMgYSBtaW5vciBjaGFuZ2UsIGJ1dCBzdGlsbCBub3Qgc2lnbmlmaWNhbnQuCgpgYGB7cn0KbmhsXzIgPC0gbmhsICU+JSAKICBmaWx0ZXIoU2FsYXJ5X3NjYWxlID4gMSkKCmdncGxvdChuaGxfMiwgYWVzKFNhbGFyeV9zY2FsZSkpKwogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAnZ3JlZW4nKSArIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgMTUsIGJ5ID0gMSkpCmBgYAoKVGhpcyBzaG93cyBhIHNpZ25pZmljYW50IGNoYW5nZSBpbiBkaXN0cmlidXRpb24sIGEgbXVjaCBtb3JlIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBwb3NpdGl2ZSBza2V3LgoKYGBge3J9CmNvci50ZXN0KG5obF8yJFBUUywgbmhsXzIkU2FsYXJ5KQpgYGAKCldlIGNvdWxkIGFsc28gZmlsdGVyIHRoZSBkYXRhc2V0IGZvciBwbGF5ZXJzIHdobyBwbGF5ZWQgYXQgbGVhc3QgaGFsZiBvZiBhIHNlYXNvbiwgb3IgNDEgZ2FtZXMuCgpgYGB7cn0KbmhsXzM8LSBuaGwgJT4lIAogIGZpbHRlcihHUCA+PSA0MSkKCmdncGxvdChuaGxfMywgYWVzKFNhbGFyeV9zY2FsZSkpKwogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAxOjMwKSArIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgMTUsIGJ5ID0gMSkpCmBgYAoKU2luY2Ugd2UgYXJlIGdvaW5nIHRvIHByZWRpY3Qgc2FsYXJ5LCBpdCdzIGJlc3Qgbm90IHRvIGZpbHRlciB0aGUgZGF0YSBiYXNlZCBvbiBzYWxhcnkgdG8gYXZvaWQgdG9vIG11Y2ggbWFuaXB1bGF0aW9uIG9uIHRoZSBtb2RlbC4gU28gd2Ugd2lsbCB1c2UgdGhlIDQxIGdhbWVzIHBsYXllZCB0aHJlc2hvbGQgZm9yIG91ciBkYXRhc2V0LgoKYGBge3J9CmdncGxvdChuaGxfMykrIAogIGdlb21fcG9pbnQoYWVzKHggPSBQVFMsIHkgPSBTYWxhcnksIGNvbG9yID0gUFRTKSkKYGBgCgpgYGB7cn0KY29yLnRlc3QobmhsXzMkUFRTLCBuaGxfMyRTYWxhcnkpCmBgYAoKIyMjIEZpbmRpbmcgaGlnaGVzdCBwYWlkIHBsYXllcnMsIGxlYWRpbmcgc2NvcmVycwoKV2UgY2FuIGZpbHRlciB0aGlzIGRhdGFzZXQgZm9yIHNvbWUgb2YgdGhlIG1vc3QgY29tbW9ubHkgbWVhc3VyZWQgc3RhdGlzdGljcyBpbiBob2NrZXk6IHBvc2l0aW9uLCB0ZWFtLCBnYW1lcyBwbGF5ZWQsIGdvYWxzLCBhc3Npc3RzLCBwb2ludHMsIHRpbWUgb24gaWNlIHBlciBnYW1lLCBwZW5hbHR5IG1pbnV0ZXMsIGV4cGVjdGVkIGdvYWxzIGZvciwgYW5kIHBvaW50cyBzaGFyZS4KCmBgYHtyfQpjb2xuYW1lcyhuaGxfMykKYGBgCgpgYGB7cn0KbmhsXzQgPC0gbmhsXzMgJT4lIAogIGFycmFuZ2UoZGVzYyhTYWxhcnkpKSAlPiUgCiAgc2VsZWN0KFNhbGFyeSwgU2FsYXJ5X3NjYWxlLCBMYXN0Lk5hbWUsIEZpcnN0Lk5hbWUsIFBvc2l0aW9uLFRlYW0sIEdQLCBHLCBBLCBQVFMsIHBsdXNfbWludXMsIEVfcGx1c19taW51cywgRk9fcGN0LCBUT0kuR1AsIFRPSV9wY3QsIFBJTSwgeEdGLCBHRiwgeEdBLCBHQSwgeEdBLCBQUykKCgpgYGAKCmBgYHtyfQpuaGxfNApgYGAKCldlIGNhbiBzZWUgdGhhdCBKb25hdGhhbiBUb2V3cyBhbmQgUGF0cmljayBLYW5lIHdlcmUgdGhlIGhpZ2hlc3QgcGFpZCBwbGF5ZXJzIHRoYXQgc2Vhc29uIHdpdGggc2FsYXJpZXMgb2YgXCQxMy44IG1pbGxpb24sIHNjb3JpbmcgNTggYW5kIDg5IHBvaW50cywgcmVzcGVjdGl2ZWx5LiBXaG8gd2FzIHRoZSBsZWFkaW5nIHBvaW50cyBzY29yZXIgdGhhdCBzZWFzb24/CgojIyMjIExlYWRpbmcgU2NvcmVyCgpgYGB7cn0KbGVhZGluZ19zY29yZTwtIG5obF80ICU+JSAKICBmaWx0ZXIoUFRTID09IG1heChQVFMpKQoKbGVhZGluZ19zY29yZQoKYGBgCgpTbyB0aGUgaGlnaGVzdCBwYWlkIHBsYXllciBpbiBQYXRyaWNrIEthbmUgd2FzIHRpZWQgd2l0aCBTaWRuZXkgQ3Jvc2J5LCBtYWtpbmcgXCQxMC45IG1pbGxpb24gZm9yIHRoZSBtb3N0IHBvaW50cyBpbiB0aGUgbGVhZ3VlIHdpdGggODkuIExldCdzIHNlZSB3aGljaCBwbGF5ZXJzIHNjb3JlZCBtb3JlIHBvaW50cyB0aGFuIEpvbmF0aGFuIFRvZXdzIHdpdGggNTggcG9pbnRzLgoKIyMjIyBNb3JlIFBvaW50cyBUaGFuIFRvZXdzCgpgYGB7cn0KbW9yZV90aGFuX3RvZXdzPC0gbmhsXzQgJT4lIAogIGZpbHRlcihuaGxfNCRQVFM+bmhsXzQkUFRTWzJdKSAlPiUgCiAgYXJyYW5nZShkZXNjKFBUUykpCgptb3JlX3RoYW5fdG9ld3MKYGBgCgpXZSBjYW4gc2VlIGV2ZW4gdGhvdWdoIFRvZXdzIHdhcyB0aWVkIGZvciB0aGUgaGlnaGVzdCBwbGF5ZXIgdGhhdCB5ZWFyLCB0aGVyZSB3ZXJlIDM1IHBsYXllcnMgd2l0aCBtb3JlIHBvaW50cyB0aGFuIEpvbmF0aGFuIFRvZXdzIHRoYXQgc2Vhc29uLgoKIyMjIyBIaWdoZXN0IEZhY2Utb2ZmIFdpbiBQZXJjZW50YWdlCgpUb2V3cyBpcyBrbm93biBmb3IgYmVpbmcgYSB2ZXJ5IGdvb2QgZmFjZS1vZmYgcGxheWVyLCBzbyBsZXRzIHNlZSBob3cgaGlzIGZhY2Ugb2ZmIHBlcmNlbnRhZ2UgcmFua2VkIHRoYXQgc2Vhc29uLgoKYGBge3J9CmZhY2VvZmY8LSBuaGxfNCAlPiUgCiAgZmlsdGVyKFBvc2l0aW9uID09ICdDJykgJT4lIAogIGFycmFuZ2UoZGVzYyhGT19wY3QpKSAlPiUgCiAgc2VsZWN0KFNhbGFyeSwgTGFzdC5OYW1lLCBGaXJzdC5OYW1lLCBUZWFtLCBGT19wY3QpCgpmYWNlb2ZmCgpgYGAKCkl0J3MgaW1wb3J0YW50IHRvIG5vdGUsIHRoZSBzdGF0aXN0aWNzIHNlbGVjdGVkIGRlc2NyaWJlIHBsYXllciBzdGF0aXN0aWNzIGFzIGdvYWxpZXMgYXJlIGV2YWx1YXRlZCB2ZXJ5IGRpZmZlcmVudGx5LCBzbyB3ZSBzaG91bGQgZmlsdGVyIGFueSBnb2FsdGVuZGVycyBvdXQgb2YgdGhpcyBkYXRhc2V0IGJlZm9yZSB3ZSBidWlsZCBvdXIgbW9kZWwuCgpgYGB7cn0KbmhsXzQ8LSBuaGxfNCAlPiUgCiAgZmlsdGVyKFBvc2l0aW9uICE9IEcpCmBgYAoKV2UgY2FuIGdyb3VwIGJ5IHBvc2l0aW9uIHRvIGZpbmQgc29tZSBzYWxhcnkgaW5mb3JtYXRpb24sIGJ1dCBzaG91bGQgc2VwYXJhdGUgYnkgZm9yd2FyZCB2cyBkZWZlbnNlLCBhcyBtYW55IGZvcndhcmQgcG9zaXRpb25zIGFyZSBsaXN0ZWQgYXMgIkxXL1JXIiBvciAiQy9MVyIgYW5kIHdlIGRvbid0IHdhbnQgdGhvc2UgdHJlYXRlZCBhcyBzZXBhcmF0ZSBncm91cHMuCgpgYGB7cn0KbmhsXzQ8LSBuaGxfNCAlPiUgCiAgbXV0YXRlKGZ3ZF9vcl9kID0gaWZlbHNlKFBvc2l0aW9uID09ICdEJywgJ0QnLCdGb3J3YXJkJykpCgpuaGxfNApgYGAKCiMjIERlc2NyaXB0aXZlIFN0YXRpc3RpY3MKCldlIGNhbiB1c2UgdGhpcyB0byBncm91cCBieSBmb3J3YXJkcyBhbmQgZGVmZW5zZSB0byBmaW5kIG1lYW4sIG1heCwgYW5kIG1pbmltdW0gc2FsYXJpZXMgYW1vbmdzdCB0aGUgNDA0IHBsYXllcnMgd2l0aCA0MSBvciBtb3JlIGdhbWVzIHBsYXllZC4KCmBgYHtyfQptYXhfc2FsX3Bvc2l0aW9uPC0gbmhsXzQgJT4lIAogIGdyb3VwX2J5KGZ3ZF9vcl9kKSAlPiUgCiAgc3VtbWFyaXNlKG1heF9zYWxhcnkgPSBtYXgoU2FsYXJ5KSwgbWVhbl9zYWxhcnkgPSBtZWFuKFNhbGFyeSksIG1pbl9zYWxhcnkgPSBtaW4oU2FsYXJ5KSkKCm1heF9zYWxfcG9zb3Rpb24KICAKYGBgCgpJdCBtYWtlcyBzZW5zZSBmb3IgdGhlIG1pbmltdW0gc2FsYXJpZXMgdG8gYmUgZXF1YWwgZm9yIGJvdGggZ3JvdXBzIGdpdmVuIHRoZSBsZWFndWUgbWluaW11bSBzYWxhcnkuIEludGVyZXN0aW5nbHksIGZvcndhcmRzIGhhdmUgYSBoaWdoZXIgbWF4aW11bSBzYWxhcnkgYnV0IGxvd2VyIG1lYW4gc2FsYXJ5LgoKV2UgY2FuIGZpbmQgdGhlIG1heGltdW0sIG1lYW4sIGFuZCBtaW5pbXVtIHNhbGFyaWVzIGJ5IHRlYW06CgpgYGB7cn0KbWF4X3NhbF90ZWFtPC0gbmhsXzQgJT4lIAogIGdyb3VwX2J5KFRlYW0pICU+JSAKICBzdW1tYXJpc2UobWF4X3NhbGFyeSA9IG1heChTYWxhcnkpLCBtZWFuX3NhbGFyeSA9IG1lYW4oU2FsYXJ5KSwgbWluX3NhbGFyeSA9IG1pbihTYWxhcnkpKQoKbWF4X3NhbF90ZWFtCmBgYAoKTGV0J3MgZXhhbWluZSB0aGUgdmFyaWFuY2UgYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBpbiBzYWxhcnkgd2l0aGluIHBvc2l0aW9uIGdyb3VwczoKCmBgYHtyfQp2YXJfc2FsYXJ5PC0gbmhsXzQgJT4lIAogIGdyb3VwX2J5KGZ3ZF9vcl9kKSAlPiUgCiAgc3VtbWFyaXNlKHNhbGFyeV92YXJpYW5jZSA9IHZhcihTYWxhcnkpLCBzYWxhcnlfc2QgPSBzZChTYWxhcnkpKQoKdmFyX3NhbGFyeQpgYGAKCkJlZm9yZSBidWlsZGluZyBvdXIgbW9kZWwsIGxldHMgYXNzZXNzIHZhbHVlcyB0aGF0IG1heSBiZSBoaWdobHkgY29ycmVsYXRlZCB3aXRoIG9uZSBhbm90aGVyLCBzbyB3ZSBjYW4gYXZvaWQgdXNpbmcgYm90aCBmYWN0b3JzIGFuZCBhcnRpZmljaWFsbHkgaW5jcmVhc2luZyBvdXIgYWRqdXN0ZWQgUiAtIHNxdWFyZWQgYnkgYWRkaW5nIGFub3RoZXIgcHJlZGljdG9yIGlmIGl0IGlzIGFscmVhZHkgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBhbm90aGVyLgoKV2Uga25vdyB0aGF0IHBvaW50cyBpbmNsdWRlcyBhc3Npc3RzIGFuZCBnb2Fscywgc28gdGhvc2UgdmFsdWVzIHNob3VsZCBiZSBoaWdobHkgY29ycmVsYXRlZC4KCmBgYHtyfQpjb3IudGVzdChuaGxfNCRQVFMsIG5obF80JEcpCmBgYAoKYGBge3J9CmNvci50ZXN0KG5obF80JFBUUywgbmhsXzQkQSkKYGBgCgpTbyB1c2luZyBqdXN0IHBvaW50cyBpbiBvdXIgbW9kZWwgc2hvdWxkIGJlIHN1ZmZpY2llbnQuCgpgYGB7cn0KYmlzZXJpYWwobmhsXzQkUFRTLCBuaGxfNCRmd2Rfb3JfZCkKYGBgCgpQb2ludHMgaGFzIGEgbXVjaCBsb3dlciBjb3JyZWxhdGlvbiB3aXRoIHBvc2l0aW9uLCBzbyBpdCBtYXkgYmUgYWNjZXB0YWJsZSB0byBpbmNsdWRlIGZvcndhcmQgdnMgZGVmZW5zZSBpbiBvdXIgbW9kZWwuCgpgYGB7cn0KY29yLnRlc3QobmhsXzQkUFRTLCBuaGxfNCR4R0YpCmBgYAoKVGhlcmUgaXMgYWxzbyBhIGhpZ2ggY29ycmVhbHRpb24gYmV0d2VlbiBwb2ludHMgYW5kIGV4cGVjdGVkIGdvYWxzIGZvciwgd2hpY2ggcmVwcmVzZW50cyB0aGUgZXhwZWN0ZWQgYW1vdW50IG9mIGdvYWxzIHNjb3JlZCBieSBhIHBsYXllcidzIHRlYW0gd2hpbGUgdGhhdCBwbGF5ZXIgaXMgb24gdGhlIGljZS4gSSBleHBlY3QgdGhpcyB3aWxsIGJlIGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggcG9pbnRzIHNoYXJlOgoKYGBge3J9CmNvci50ZXN0KG5obF80JFBTLCBuaGxfNCR4R0YpCmBgYAoKIyBCdWlsZGluZyBNb2RlbHMKCiMjIyMjIFByZWRpY3RpbmcgU2FsYXJ5IGZyb20gcG9pbnRzIGFsb25lOgoKYGBge3J9Cm1vZF8xPC0gbG0oU2FsYXJ5fiBQVFMsIGRhdGEgPSBuaGxfNCkKc3VtbWFyeShtb2RfMSkKYGBgCgpIZXJlIHRoZSBtb2RlbCBoYXMgYSBzaWduaWZpY2FudCBwLWFsdWUsIGFkanVzdGVkIFIgc3F1YXJlZCBvZiAwLjI2MzEKCiMjIyMjIFByZWRpY3RpbmcgU2FsYXJ5IGZyb20gUFRTIGFuZCBwbHVzIG1pbnVzCgpgYGB7cn0KbW9kXzIgPC0gbG0oU2FsYXJ5IH4gUFRTICsgcGx1c19taW51cywgZGF0YSA9IG5obF80KQpzdW1tYXJ5KG1vZF8yKQpgYGAKClRoZSBhZGp1c3RlZCBSLXNxdWFyZWQgaXMgc2xpZ2h0bHkgbG93ZXIsIHdpdGggUFRTIHJlbWFpbmluZyBzaWduaWZpY2FudCBidXQgcGx1cy1taW51cyBpbnNpZ25pZmljYW50LgoKYGBge3J9CnByaW50KEFJQyhtb2RfMSwgayA9IDEpKQoKcHJpbnQoQUlDKG1vZF8yLCBrPTIpKQpgYGAKCkFzIGV4cGVjdGVkLCB0aGUgbG93ZXIgQUlDIGNvcnJlc3BvbmRzIHRvIHRoZSBmaXJzdCBtb2RlbCBhcyBhIGJldHRlciBmaXR0aW5nIG1vZGVsLgoKIyMjIyMgQWRkaW5nIGV4cGVjdGVkIGdvYWxzIGZvciBhbmQgYWdhaW5zdCB0byB0aGUgbW9kZWwKCkV4cGVjdGVkIEdGIGFuZCBleHBlY3RlZCBHQSBjYW4gZGVzY3JpYmUgaG93IGEgcGxheWVyJ3MgdGVhbSBpcyBwZXJmb3JtaW5nIHdpdGggdGhhdCBwbGF5ZXIgb24gdGhlIGljZSwgZXZlbiBpZiB0aGUgcGxheWVyIGRvZXNuJ3QgZGlyZWN0bHkgaW5mbHVlbmNlIHRoZSBnb2FscyBvY2N1cnJpbmcuIFdlIGNhbiBtb2RlbCBTYWxhcnkgb24geEdGIGFuZCBHRiwgYW5kIHhHQSBhbmQgR0EgdG8gc2VlIGlmIGV4cGVjdGVkIHZlcnN1cyBhY3R1YWwgc3RhdHMgcHJvdmUgdG8gYmUgbW9yZSBzaWduaWZpY2FudCBwcmVkaWN0b3JzLgoKYGBge3J9Cm1vZGVsX0dGPC0gbG0oU2FsYXJ5IH54R0YrIEdGLCBkYXRhID0gbmhsXzQpCnN1bW1hcnkobW9kZWxfR0YpCmBgYAoKSW4gdGhpcyBjYXNlLCBleHBlY3RlZCBnb2FscyBmb3IgaXMgYSBtb3JlIHNpZ25pZmljYW50IHByZWRpY3RvciB0aGFuIGdvYWxzIGZvci4gV2UgY2FuIGV2YWx1YXRlIHRoZSBzYW1lIHRoaW5nIHdpdGggZ29hbHMgYWdhaW5zdDoKCmBgYHtyfQptb2RlbF9HQSA8LSBsbShTYWxhcnkgfiB4R0EgKyBHQSwgZGF0YSA9IG5obF80KQpzdW1tYXJ5KG1vZGVsX0dBKQpgYGAKCkluIHRoaXMgY2FzZSwgZXhwZWN0ZWQgZ29hbHMgYWdhaW5zdCBpcyBub3Qgc2lnbmlmaWNhbnQsIHRoZSBpbnRlcmNlcHQgaXMgbm90IHNpZ25pZmljYW50LCBhbmQgZ29hbHMgYWdhaW5zdCBvbmx5IG1lZXRzIGEgMC4wNSBzaWduaWZpY2FuY2UgbGV2ZWwuCgpgYGB7cn0KbW9kXzMgPC0gbG0oU2FsYXJ5IH4gUFRTICsgeEdGLCBkYXRhID0gbmhsXzQpCnN1bW1hcnkobW9kXzMpCmBgYAoKSW4gdGhlc2UgbW9kZWxzLCB4R0YsIFBUUywgYW5kIEdBIGFsbCBzaG93IHNpZ25pZmljYW50IGFmZmVjdHMgb24gdGhlIGRlcGVuZGVudCB2YXJpYWJsZSwgU2FsYXJ5LiBXZSBjYW4gY3JlYXRlIGEgbW9kZWwgd2l0aCBhbGwgdGhyZWU6CgpgYGB7cn0KbW9kXzQgPC0gbG0oU2FsYXJ5fiBQVFMgKyBHQSArIHhHRiwgZGF0YSA9IG5obF80KQpzdW1tYXJ5KG1vZF80KQpgYGAKCldoZW4gaW5jbHVkaW5nIGFsbCB0aHJlZSB2YXJpYWJsZXMsIEdBIGxvc2VzIHNpZ25pZmljYW5jZS4gVGhpcyBjb3VsZCBiZSBleHBsYWluZWQgYnkgYSByZWxhdGl2ZWx5IHN0cm9uZyBjb3JyZWxhdGlvbiBiZXR3ZWVuIHhHRiBhbmQgR0EsIHNvIGluY2x1ZGluZyBib3RoIHdpbGwgbWFrZSBvbmUgdmFyaWFibGUgZHJvcCBvdXQgb2YgdGhlIHJhbmdlIG9mIHNpZ25pZmljYW5jZS4KCmBgYHtyfQpjb3IudGVzdChuaGxfNCR4R0YsIG5obF80JEdBKQpgYGAKCldlIGNhbiBjb25maXJtIHRoYXQgbW9kZWwgMyB3aXRoIGZld2VyIHByZWRpY3RvcnMgaXMgdGhlIGJldHRlciBtb2RlbCBieSBleGFtaW5pbmcgQUlDLiBOb3RhdmJseSwgZWFjaCBvZiB0aGVzZSBtb2RlbHMgaGF2ZSBhIGxvd2VyIEFJQywgYW5kIHRoZXJlZm9yZSBiZXR0ZXIgZml0LCB0aGFuIHJlZ3Jlc3NpbmcgU2FsYXJ5IG9uIHBvaW50cyBhbG9uZS4KCmBgYHtyfQpBSUMobW9kXzQsIGsgPSAzKQpgYGAKCmBgYHtyfQpBSUMobW9kXzMsIGs9MikKYGBgCgpMYXN0bHksIHdlIGNhbiBleGFtaW5lIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBTYWxhcnkgYW5kIGp1c3QgeEdGOgoKYGBge3J9Cm1vZF94R0Y8LSBsbShTYWxhcnl+IHhHRiwgZGF0YSA9IG5obF80KQpzdW1tYXJ5KG1vZF94R0YpCmBgYAoKYGBge3J9CkFJQyhtb2RfeEdGKQpgYGAKCkluIHN1bW1hcnksIG9mIHRoZSBzZWxlY3RlZCBwcmVkaWN0b3JzIGFuZCBjb21iaW5hdGlvbnMsIHhHRiBhbmQgUFRTIGFyZSB0aGUgYmVzdCBwcmVkaWN0b3JzIG9mIFNhbGFyeSB3aGVuIHVzZWQgYXMgYSBtdWx0aXZhcmlhdGUgcmVncmVzc2lvbiwgcmF0aGVyIHRoYW4gZWl0aGVyIHByZWRpY3RvciBhbG9uZS4KCiMgVmlzdWFsaXppbmcgdGhlIHByZWRpY3Rvci9yZXNwb25zZSByZWxhdGlvbnNoaXAgYW5kIG1vZGVsCgpXZSBjYW4gdmlldyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gUFRTLCB4R0YsIGFuZCBTYWxhcnkoc2NhbGVkKSBpbiBhIDNEIHBsb3Q6CgpgYGB7cn0KcGxvdF9seSh4ID0gbmhsXzQkUFRTLCB5ID0gbmhsXzQkU2FsYXJ5X3NjYWxlLCB6ID0gbmhsXzQkeEdGLCBjb2xvciA9IG5obF80JFBUUykgJT4lIAogIGxheW91dChzY2VuZT1saXN0KHhheGlzID0gbGlzdCh0aXRsZSA9ICdQb2ludHMnKSwgeWF4aXMgPSBsaXN0KHRpdGxlPSdTYWxhcnkgaW4gTWlsbGlvbnMnKSx6YXhpcyA9IGxpc3QodGl0bGUgPSAnRXhwZWN0ZWQgR29hbHMgRm9yJykpKQpgYGAKClRoZSByZWxhdGlvbnNoaXBzIGNhbiBhbHNvIGJlIHZpZXdlZCBpbiB0d28gZGltZW5zaW9ucyB1c2luZyB0aGUgJ2NhcicgbGlicmFyeS4gSW4gdGhpcyB2aXN1YWxpemF0aW9uLCB0aGUgcGxvdHRlZCBsaW5lIHNob3dzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZ2l2ZW4gcHJlZGljdG9yIGFuZCB0aGUgcmVzcG9uc2UgdmFyaWFibGUgd2l0aCB0aGUgb3RoZXIgcHJlZGljdG9yIGhlbGQgY29uc3RhbnQuCgpgYGB7cn0KYXZQbG90cyhtb2RfMykKYGBgCg==